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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts,
5 * 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.thoughtworks.xstream.converters.ConversionException;
28 import com.thoughtworks.xstream.io.StreamException;
29 import com.thoughtworks.xstream.io.xml.XppDriver;
30 import hudson.DescriptorExtensionList;
31 import hudson.ExtensionPoint;
32 import hudson.Functions;
33 import hudson.Indenter;
34 import hudson.Util;
35 import hudson.model.Descriptor.FormException;
36 import hudson.model.labels.LabelAtomPropertyDescriptor;
37 import hudson.scm.ChangeLogSet;
38 import hudson.scm.ChangeLogSet.Entry;
39 import hudson.search.CollectionSearchIndex;
40 import hudson.search.SearchIndexBuilder;
41 import hudson.security.ACL;
42 import hudson.security.AccessControlled;
43 import hudson.security.Permission;
44 import hudson.security.PermissionGroup;
45 import hudson.security.PermissionScope;
46 import hudson.tasks.UserAvatarResolver;
47 import hudson.util.AlternativeUiTextProvider;
48 import hudson.util.AlternativeUiTextProvider.Message;
49 import hudson.util.DescribableList;
50 import hudson.util.DescriptorList;
51 import hudson.util.FormApply;
52 import hudson.util.IOException2;
53 import hudson.util.RunList;
54 import hudson.util.XStream2;
55 import hudson.views.ListViewColumn;
56 import hudson.widgets.Widget;
57 import jenkins.model.Jenkins;
58 import jenkins.util.ProgressiveRendering;
59 import net.sf.json.JSON;
60 import net.sf.json.JSONArray;
61 import net.sf.json.JSONObject;
62 import org.kohsuke.stapler.HttpResponse;
63 import org.kohsuke.stapler.HttpResponses;
64 import org.kohsuke.stapler.Stapler;
65 import org.kohsuke.stapler.StaplerRequest;
66 import org.kohsuke.stapler.StaplerResponse;
67 import org.kohsuke.stapler.WebMethod;
68 import org.kohsuke.stapler.export.Exported;
69 import org.kohsuke.stapler.export.ExportedBean;
70 import org.kohsuke.stapler.interceptor.RequirePOST;
71
72 import javax.servlet.ServletException;
73 import javax.servlet.http.HttpServletResponse;
74 import javax.xml.transform.Source;
75 import javax.xml.transform.Transformer;
76 import javax.xml.transform.TransformerException;
77 import javax.xml.transform.TransformerFactory;
78 import javax.xml.transform.stream.StreamResult;
79 import javax.xml.transform.stream.StreamSource;
80 import java.io.BufferedInputStream;
81 import java.io.ByteArrayInputStream;
82 import java.io.IOException;
83 import java.io.InputStream;
84 import java.io.StringWriter;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.Calendar;
88 import java.util.Collection;
89 import java.util.Collections;
90 import java.util.Comparator;
91 import java.util.GregorianCalendar;
92 import java.util.HashMap;
93 import java.util.HashSet;
94 import java.util.List;
95 import java.util.Map;
96 import java.util.Set;
97 import java.util.logging.Level;
98 import java.util.logging.Logger;
99
100 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
101 import static jenkins.model.Jenkins.*;
102 import org.kohsuke.accmod.Restricted;
103 import org.kohsuke.accmod.restrictions.NoExternalUse;
104
105 /**
106 * Encapsulates the rendering of the list of {@link TopLevelItem}s
107 * that {@link Jenkins} owns.
108 *
109 * <p>
110 * This is an extension point in Hudson, allowing different kind of
111 * rendering to be added as plugins.
112 *
113 * <h2>Note for implementors</h2>
114 * <ul>
115 * <li>
116 * {@link View} subtypes need the <tt>newViewDetail.jelly</tt> page,
117 * which is included in the "new view" page. This page should have some
118 * description of what the view is about.
119 * </ul>
120 *
121 * @author Kohsuke Kawaguchi
122 * @see ViewDescriptor
123 * @see ViewGroup
124 */
125 @ExportedBean
126 public abstract class View extends AbstractModelObject implements AccessControlled, Describable<View>, ExtensionPoint, Saveable {
127
128 /**
129 * Container of this view. Set right after the construction
130 * and never change thereafter.
131 */
132 protected /*final*/ ViewGroup owner;
133
134 /**
135 * Name of this view.
136 */
137 protected String name;
138
139 /**
140 * Message displayed in the view page.
141 */
142 protected String description;
143
144 /**
145 * If true, only show relevant executors
146 */
147 protected boolean filterExecutors;
148
149 /**
150 * If true, only show relevant queue items
151 */
152 protected boolean filterQueue;
153
154 protected transient List<Action> transientActions;
155
156 /**
157 * List of {@link ViewProperty}s configured for this view.
158 * @since 1.406
159 */
160 private volatile DescribableList<ViewProperty,ViewPropertyDescriptor> properties = new PropertyList(this);
161
162 protected View(String name) {
163 this.name = name;
164 }
165
166 protected View(String name, ViewGroup owner) {
167 this.name = name;
168 this.owner = owner;
169 }
170
171 /**
172 * Gets all the items in this collection in a read-only view.
173 */
174 @Exported(name="jobs")
175 public abstract Collection<TopLevelItem> getItems();
176
177 /**
178 * Gets the {@link TopLevelItem} of the given name.
179 */
180 public TopLevelItem getItem(String name) {
181 return getOwnerItemGroup().getItem(name);
182 }
183
184 /**
185 * Alias for {@link #getItem(String)}. This is the one used in the URL binding.
186 */
187 public final TopLevelItem getJob(String name) {
188 return getItem(name);
189 }
190
191 /**
192 * Checks if the job is in this collection.
193 */
194 public abstract boolean contains(TopLevelItem item);
195
196 /**
197 * Gets the name of all this collection.
198 *
199 * @see #rename(String)
200 */
201 @Exported(visibility=2,name="name")
202 public String getViewName() {
203 return name;
204 }
205
206 /**
207 * Renames this view.
208 */
209 public void rename(String newName) throws Failure, FormException {
210 if(name.equals(newName)) return; // noop
211 checkGoodName(newName);
212 if(owner.getView(newName)!=null)
213 throw new FormException(Messages.Hudson_ViewAlreadyExists(newName),"name");
214 String oldName = name;
215 name = newName;
216 owner.onViewRenamed(this,oldName,newName);
217 }
218
219 /**
220 * Gets the {@link ViewGroup} that this view belongs to.
221 */
222 public ViewGroup getOwner() {
223 return owner;
224 }
225
226 /**
227 * Backward-compatible way of getting {@code getOwner().getItemGroup()}
228 */
229 public ItemGroup<? extends TopLevelItem> getOwnerItemGroup() {
230 try {
231 return _getOwnerItemGroup();
232 } catch (AbstractMethodError e) {
233 return Hudson.getInstance();
234 }
235 }
236
237 /**
238 * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
239 * on BugParade for more details.
240 */
241 private ItemGroup<? extends TopLevelItem> _getOwnerItemGroup() {
242 return owner.getItemGroup();
243 }
244
245 public View getOwnerPrimaryView() {
246 try {
247 return _getOwnerPrimaryView();
248 } catch (AbstractMethodError e) {
249 return null;
250 }
251 }
252
253 private View _getOwnerPrimaryView() {
254 return owner.getPrimaryView();
255 }
256
257 public List<Action> getOwnerViewActions() {
258 try {
259 return _getOwnerViewActions();
260 } catch (AbstractMethodError e) {
261 return Hudson.getInstance().getActions();
262 }
263 }
264
265 private List<Action> _getOwnerViewActions() {
266 return owner.getViewActions();
267 }
268
269 /**
270 * Message displayed in the top page. Can be null. Includes HTML.
271 */
272 @Exported
273 public String getDescription() {
274 return description;
275 }
276
277 /**
278 * Gets the view properties configured for this view.
279 * @since 1.406
280 */
281 public DescribableList<ViewProperty,ViewPropertyDescriptor> getProperties() {
282 // readResolve was the best place to do this, but for compatibility reasons,
283 // this class can no longer have readResolve() (the mechanism itself isn't suitable for class hierarchy)
284 // see JENKINS-9431
285 //
286 // until we have that, putting this logic here.
287 synchronized (PropertyList.class) {
288 if (properties == null) {
289 properties = new PropertyList(this);
290 } else {
291 properties.setOwner(this);
292 }
293 return properties;
294 }
295 }
296
297 /**
298 * Returns all the {@link LabelAtomPropertyDescriptor}s that can be potentially configured
299 * on this label.
300 */
301 public List<ViewPropertyDescriptor> getApplicablePropertyDescriptors() {
302 List<ViewPropertyDescriptor> r = new ArrayList<ViewPropertyDescriptor>();
303 for (ViewPropertyDescriptor pd : ViewProperty.all()) {
304 if (pd.isEnabledFor(this))
305 r.add(pd);
306 }
307 return r;
308 }
309
310 public void save() throws IOException {
311 // persistence is a part of the owner
312 // due to initialization timing issue, it can be null when this method is called
313 if (owner != null) {
314 owner.save();
315 }
316 }
317
318 /**
319 * List of all {@link ViewProperty}s exposed primarily for the remoting API.
320 * @since 1.406
321 */
322 @Exported(name="property",inline=true)
323 public List<ViewProperty> getAllProperties() {
324 return getProperties().toList();
325 }
326
327 public ViewDescriptor getDescriptor() {
328 return (ViewDescriptor) Jenkins.getInstance().getDescriptorOrDie(getClass());
329 }
330
331 public String getDisplayName() {
332 return getViewName();
333 }
334
335 public String getNewPronoun() {
336 return AlternativeUiTextProvider.get(NEW_PRONOUN, this, Messages.AbstractItem_Pronoun());
337 }
338
339 /**
340 * By default, return true to render the "Edit view" link on the page.
341 * This method is really just for the default "All" view to hide the edit link
342 * so that the default Hudson top page remains the same as before 1.316.
343 *
344 * @since 1.316
345 */
346 public boolean isEditable() {
347 return true;
348 }
349
350 /**
351 * If true, only show relevant executors
352 */
353 public boolean isFilterExecutors() {
354 return filterExecutors;
355 }
356
357 /**
358 * If true, only show relevant queue items
359 */
360 public boolean isFilterQueue() {
361 return filterQueue;
362 }
363
364 /**
365 * Gets the {@link Widget}s registered on this object.
366 *
367 * <p>
368 * For now, this just returns the widgets registered to Hudson.
369 */
370 public List<Widget> getWidgets() {
371 return Collections.unmodifiableList(Jenkins.getInstance().getWidgets());
372 }
373
374 /**
375 * If this view uses &lt;t:projectView> for rendering, this method returns columns to be displayed.
376 */
377 public Iterable<? extends ListViewColumn> getColumns() {
378 return ListViewColumn.createDefaultInitialColumnList();
379 }
380
381 /**
382 * If this view uses &lt;t:projectView> for rendering, this method returns the indenter used
383 * to indent each row.
384 */
385 public Indenter getIndenter() {
386 return null;
387 }
388
389 /**
390 * If true, this is a view that renders the top page of Hudson.
391 */
392 public boolean isDefault() {
393 return getOwnerPrimaryView()==this;
394 }
395
396 public List<Computer> getComputers() {
397 Computer[] computers = Jenkins.getInstance().getComputers();
398
399 if (!isFilterExecutors()) {
400 return Arrays.asList(computers);
401 }
402
403 List<Computer> result = new ArrayList<Computer>();
404
405 HashSet<Label> labels = new HashSet<Label>();
406 for (Item item : getItems()) {
407 if (item instanceof AbstractProject<?, ?>) {
408 labels.addAll(((AbstractProject<?, ?>) item).getRelevantLabels());
409 }
410 }
411
412 for (Computer c : computers) {
413 Node n = c.getNode();
414 if (n != null) {
415 if (labels.contains(null) && n.getMode() == Mode.NORMAL || !isDisjoint(n.getAssignedLabels(), labels)) {
416 result.add(c);
417 }
418 }
419 }
420
421 return result;
422 }
423
424 private boolean isDisjoint(Collection c1, Collection c2) {
425 for (Object o : c1)
426 if (c2.contains(o))
427 return false;
428 return true;
429 }
430
431 private List<Queue.Item> filterQueue(List<Queue.Item> base) {
432 if (!isFilterQueue()) {
433 return base;
434 }
435
436 Collection<TopLevelItem> items = getItems();
437 List<Queue.Item> result = new ArrayList<Queue.Item>();
438 for (Queue.Item qi : base) {
439 if (items.contains(qi.task)) {
440 result.add(qi);
441 } else
442 if (qi.task instanceof AbstractProject<?, ?>) {
443 AbstractProject<?,?> project = (AbstractProject<?, ?>) qi.task;
444 if (items.contains(project.getRootProject())) {
445 result.add(qi);
446 }
447 }
448 }
449 return result;
450 }
451
452 public List<Queue.Item> getQueueItems() {
453 return filterQueue(Arrays.asList(Jenkins.getInstance().getQueue().getItems()));
454 }
455
456 public List<Queue.Item> getApproximateQueueItemsQuickly() {
457 return filterQueue(Jenkins.getInstance().getQueue().getApproximateItemsQuickly());
458 }
459
460 /**
461 * Returns the path relative to the context root.
462 *
463 * Doesn't start with '/' but ends with '/' (except returns
464 * empty string when this is the default view).
465 */
466 public String getUrl() {
467 return isDefault() ? (owner!=null ? owner.getUrl() : "") : getViewUrl();
468 }
469
470 /**
471 * Same as {@link #getUrl()} except this returns a view/{name} path
472 * even for the default view.
473 */
474 public String getViewUrl() {
475 return (owner!=null ? owner.getUrl() : "") + "view/" + Util.rawEncode(getViewName()) + '/';
476 }
477
478 public String getSearchUrl() {
479 return getUrl();
480 }
481
482 /**
483 * Returns the transient {@link Action}s associated with the top page.
484 *
485 * <p>
486 * If views don't want to show top-level actions, this method
487 * can be overridden to return different objects.
488 *
489 * @see Jenkins#getActions()
490 */
491 public List<Action> getActions() {
492 List<Action> result = new ArrayList<Action>();
493 result.addAll(getOwnerViewActions());
494 synchronized (this) {
495 if (transientActions == null) {
496 updateTransientActions();
497 }
498 result.addAll(transientActions);
499 }
500 return result;
501 }
502
503 public synchronized void updateTransientActions() {
504 transientActions = TransientViewActionFactory.createAllFor(this);
505 }
506
507 public Object getDynamic(String token) {
508 for (Action a : getActions()) {
509 String url = a.getUrlName();
510 if (url==null) continue;
511 if(a.getUrlName().equals(token))
512 return a;
513 }
514 return null;
515 }
516
517 /**
518 * Gets the absolute URL of this view.
519 */
520 @Exported(visibility=2,name="url")
521 public String getAbsoluteUrl() {
522 return Jenkins.getInstance().getRootUrl()+getUrl();
523 }
524
525 public Api getApi() {
526 return new Api(this);
527 }
528
529 /**
530 * Returns the page to redirect the user to, after the view is created.
531 *
532 * The returned string is appended to "/view/foobar/", so for example
533 * to direct the user to the top page of the view, return "", etc.
534 */
535 public String getPostConstructLandingPage() {
536 return "configure";
537 }
538
539 /**
540 * Returns the {@link ACL} for this object.
541 */
542 public ACL getACL() {
543 return Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
544 }
545
546 public void checkPermission(Permission p) {
547 getACL().checkPermission(p);
548 }
549
550 public boolean hasPermission(Permission p) {
551 return getACL().hasPermission(p);
552 }
553
554 /**
555 * Called when a job name is changed or deleted.
556 *
557 * <p>
558 * If this view contains this job, it should update the view membership so that
559 * the renamed job will remain in the view, and the deleted job is removed.
560 *
561 * @param item
562 * The item whose name is being changed.
563 * @param oldName
564 * Old name of the item. Always non-null.
565 * @param newName
566 * New name of the item, if the item is renamed. Or null, if the item is removed.
567 */
568 public abstract void onJobRenamed(Item item, String oldName, String newName);
569
570 @ExportedBean(defaultVisibility=2)
571 public static final class UserInfo implements Comparable<UserInfo> {
572 private final User user;
573 /**
574 * When did this user made a last commit on any of our projects? Can be null.
575 */
576 private Calendar lastChange;
577 /**
578 * Which project did this user commit? Can be null.
579 */
580 private AbstractProject project;
581
582 /** @see UserAvatarResolver */
583 String avatar;
584
585 UserInfo(User user, AbstractProject p, Calendar lastChange) {
586 this.user = user;
587 this.project = p;
588 this.lastChange = lastChange;
589 }
590
591 @Exported
592 public User getUser() {
593 return user;
594 }
595
596 @Exported
597 public Calendar getLastChange() {
598 return lastChange;
599 }
600
601 @Exported
602 public AbstractProject getProject() {
603 return project;
604 }
605
606 /**
607 * Returns a human-readable string representation of when this user was last active.
608 */
609 public String getLastChangeTimeString() {
610 if(lastChange==null) return "N/A";
611 long duration = new GregorianCalendar().getTimeInMillis()- ordinal();
612 return Util.getTimeSpanString(duration);
613 }
614
615 public String getTimeSortKey() {
616 if(lastChange==null) return "-";
617 return Util.XS_DATETIME_FORMATTER.format(lastChange.getTime());
618 }
619
620 public int compareTo(UserInfo that) {
621 long rhs = that.ordinal();
622 long lhs = this.ordinal();
623 if(rhs>lhs) return 1;
624 if(rhs<lhs) return -1;
625 return 0;
626 }
627
628 private long ordinal() {
629 if(lastChange==null) return 0;
630 return lastChange.getTimeInMillis();
631 }
632 }
633
634 /**
635 * Does this {@link View} has any associated user information recorded?
636 * @deprecated Potentially very expensive call; do not use from Jelly views.
637 */
638 public boolean hasPeople() {
639 return People.isApplicable(getItems());
640 }
641
642 /**
643 * Gets the users that show up in the changelog of this job collection.
644 */
645 public People getPeople() {
646 return new People(this);
647 }
648
649 /**
650 * @since 1.484
651 */
652 public AsynchPeople getAsynchPeople() {
653 return new AsynchPeople(this);
654 }
655
656 @ExportedBean
657 public static final class People {
658 @Exported
659 public final List<UserInfo> users;
660
661 public final ModelObject parent;
662
663 public People(Jenkins parent) {
664 this.parent = parent;
665 // for Hudson, really load all users
666 Map<User,UserInfo> users = getUserInfo(parent.getItems());
667 User unknown = User.getUnknown();
668 for (User u : User.getAll()) {
669 if(u==unknown) continue; // skip the special 'unknown' user
670 if(!users.containsKey(u))
671 users.put(u,new UserInfo(u,null,null));
672 }
673 this.users = toList(users);
674 }
675
676 public People(View parent) {
677 this.parent = parent;
678 this.users = toList(getUserInfo(parent.getItems()));
679 }
680
681 private Map<User,UserInfo> getUserInfo(Collection<? extends Item> items) {
682 Map<User,UserInfo> users = new HashMap<User,UserInfo>();
683 for (Item item : items) {
684 for (Job job : item.getAllJobs()) {
685 if (job instanceof AbstractProject) {
686 AbstractProject<?,?> p = (AbstractProject) job;
687 for (AbstractBuild<?,?> build : p.getBuilds()) {
688 for (Entry entry : build.getChangeSet()) {
689 User user = entry.getAuthor();
690
691 UserInfo info = users.get(user);
692 if(info==null)
693 users.put(user,new UserInfo(user,p,build.getTimestamp()));
694 else
695 if(info.getLastChange().before(build.getTimestamp())) {
696 info.project = p;
697 info.lastChange = build.getTimestamp();
698 }
699 }
700 }
701 }
702 }
703 }
704 return users;
705 }
706
707 private List<UserInfo> toList(Map<User,UserInfo> users) {
708 ArrayList<UserInfo> list = new ArrayList<UserInfo>();
709 list.addAll(users.values());
710 Collections.sort(list);
711 return Collections.unmodifiableList(list);
712 }
713
714 public Api getApi() {
715 return new Api(this);
716 }
717
718 /**
719 * @deprecated Potentially very expensive call; do not use from Jelly views.
720 */
721 public static boolean isApplicable(Collection<? extends Item> items) {
722 for (Item item : items) {
723 for (Job job : item.getAllJobs()) {
724 if (job instanceof AbstractProject) {
725 AbstractProject<?,?> p = (AbstractProject) job;
726 for (AbstractBuild<?,?> build : p.getBuilds()) {
727 for (Entry entry : build.getChangeSet()) {
728 User user = entry.getAuthor();
729 if(user!=null)
730 return true;
731 }
732 }
733 }
734 }
735 }
736 return false;
737 }
738 }
739
740 /**
741 * Variant of {@link People} which can be displayed progressively, since it may be slow.
742 * @since 1.484
743 */
744 public static final class AsynchPeople extends ProgressiveRendering { // JENKINS-15206
745
746 private final Collection<TopLevelItem> items;
747 private final User unknown;
748 private final Map<User,UserInfo> users = new HashMap<User,UserInfo>();
749 private final Set<User> modified = new HashSet<User>();
750 private final String iconSize;
751 public final ModelObject parent;
752
753 /** @see Jenkins#getAsynchPeople} */
754 public AsynchPeople(Jenkins parent) {
755 this.parent = parent;
756 items = parent.getItems();
757 unknown = User.getUnknown();
758 }
759
760 /** @see View#getAsynchPeople */
761 public AsynchPeople(View parent) {
762 this.parent = parent;
763 items = parent.getItems();
764 unknown = null;
765 }
766
767 {
768 StaplerRequest req = Stapler.getCurrentRequest();
769 iconSize = req != null ? Functions.getCookie(req, "iconSize", "32x32") : "32x32";
770 }
771
772 @Override protected void compute() throws Exception {
773 int itemCount = 0;
774 for (Item item : items) {
775 for (Job<?,?> job : item.getAllJobs()) {
776 if (job instanceof AbstractProject) {
777 AbstractProject<?,?> p = (AbstractProject) job;
778 RunList<? extends AbstractBuild<?,?>> builds = p.getBuilds();
779 int buildCount = 0;
780 for (AbstractBuild<?,?> build : builds) {
781 if (canceled()) {
782 return;
783 }
784 for (ChangeLogSet.Entry entry : build.getChangeSet()) {
785 User user = entry.getAuthor();
786 UserInfo info = users.get(user);
787 if (info == null) {
788 UserInfo userInfo = new UserInfo(user, p, build.getTimestamp());
789 userInfo.avatar = UserAvatarResolver.resolve(user, iconSize);
790 synchronized (this) {
791 users.put(user, userInfo);
792 modified.add(user);
793 }
794 } else if (info.getLastChange().before(build.getTimestamp())) {
795 synchronized (this) {
796 info.project = p;
797 info.lastChange = build.getTimestamp();
798 modified.add(user);
799 }
800 }
801 }
802 // XXX consider also adding the user of the UserCause when applicable
803 buildCount++;
804 progress((itemCount + 1.0 * buildCount / builds.size()) / (items.size() + 1));
805 }
806 }
807 }
808 itemCount++;
809 progress(1.0 * itemCount / (items.size() + /* handling User.getAll */1));
810 }
811 if (unknown != null) {
812 if (canceled()) {
813 return;
814 }
815 for (User u : User.getAll()) { // XXX nice to have a method to iterate these lazily
816 if (u == unknown) {
817 continue;
818 }
819 if (!users.containsKey(u)) {
820 UserInfo userInfo = new UserInfo(u, null, null);
821 userInfo.avatar = UserAvatarResolver.resolve(u, iconSize);
822 synchronized (this) {
823 users.put(u, userInfo);
824 modified.add(u);
825 }
826 }
827 }
828 }
829 }
830
831 @Override protected synchronized JSON data() {
832 JSONArray r = new JSONArray();
833 for (User u : modified) {
834 UserInfo i = users.get(u);
835 JSONObject entry = new JSONObject().
836 accumulate("id", u.getId()).
837 accumulate("fullName", u.getFullName()).
838 accumulate("url", u.getUrl()).
839 accumulate("avatar", i.avatar).
840 accumulate("timeSortKey", i.getTimeSortKey()).
841 accumulate("lastChangeTimeString", i.getLastChangeTimeString());
842 AbstractProject<?,?> p = i.getProject();
843 if (p != null) {
844 entry.accumulate("projectUrl", p.getUrl()).accumulate("projectFullDisplayName", p.getFullDisplayName());
845 }
846 r.add(entry);
847 }
848 modified.clear();
849 return r;
850 }
851
852 public Api getApi() {
853 return new Api(new People());
854 }
855
856 /** JENKINS-16397 workaround */
857 @Restricted(NoExternalUse.class)
858 @ExportedBean
859 public final class People {
860
861 private View.People people;
862
863 @Exported public synchronized List<UserInfo> getUsers() {
864 if (people == null) {
865 people = parent instanceof Jenkins ? new View.People((Jenkins) parent) : new View.People((View) parent);
866 }
867 return people.users;
868 }
869 }
870
871 }
872
873 void addDisplayNamesToSearchIndex(SearchIndexBuilder sib, Collection<TopLevelItem> items) {
874 for(TopLevelItem item : items) {
875
876 if(LOGGER.isLoggable(Level.FINE)) {
877 LOGGER.fine((String.format("Adding url=%s,displayName=%s",
878 item.getSearchUrl(), item.getDisplayName())));
879 }
880 sib.add(item.getSearchUrl(), item.getDisplayName());
881 }
882 }
883
884 @Override
885 public SearchIndexBuilder makeSearchIndex() {
886 SearchIndexBuilder sib = super.makeSearchIndex();
887 sib.add(new CollectionSearchIndex<TopLevelItem>() {// for jobs in the view
888 protected TopLevelItem get(String key) { return getItem(key); }
889 protected Collection<TopLevelItem> all() { return getItems(); }
890 @Override
891 protected String getName(TopLevelItem o) {
892 // return the name instead of the display for suggestion searching
893 return o.getName();
894 }
895 });
896
897 // add the display name for each item in the search index
898 addDisplayNamesToSearchIndex(sib, getItems());
899
900 return sib;
901 }
902
903 /**
904 * Accepts the new description.
905 */
906 public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
907 checkPermission(CONFIGURE);
908
909 description = req.getParameter("description");
910 save();
911 rsp.sendRedirect("."); // go to the top page
912 }
913
914 /**
915 * Accepts submission from the configuration page.
916 *
917 * Subtypes should override the {@link #submit(StaplerRequest)} method.
918 */
919 @RequirePOST
920 public final synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
921 checkPermission(CONFIGURE);
922
923 submit(req);
924
925 description = Util.nullify(req.getParameter("description"));
926 filterExecutors = req.getParameter("filterExecutors") != null;
927 filterQueue = req.getParameter("filterQueue") != null;
928
929 rename(req.getParameter("name"));
930
931 getProperties().rebuild(req, req.getSubmittedForm(), getApplicablePropertyDescriptors());
932 updateTransientActions();
933
934 save();
935
936 FormApply.success("../"+name).generateResponse(req,rsp,this);
937 }
938
939 /**
940 * Handles the configuration submission.
941 *
942 * Load view-specific properties here.
943 */
944 protected abstract void submit(StaplerRequest req) throws IOException, ServletException, FormException;
945
946 /**
947 * Deletes this view.
948 */
949 @RequirePOST
950 public synchronized void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
951 checkPermission(DELETE);
952
953 owner.deleteView(this);
954
955 rsp.sendRedirect2(req.getContextPath()+"/" + owner.getUrl());
956 }
957
958
959 /**
960 * Creates a new {@link Item} in this collection.
961 *
962 * <p>
963 * This method should call {@link ModifiableItemGroup#doCreateItem(StaplerRequest, StaplerResponse)}
964 * and then add the newly created item to this view.
965 *
966 * @return
967 * null if fails.
968 */
969 public abstract Item doCreateItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException;
970
971 public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
972 rss(req, rsp, " all builds", getBuilds());
973 }
974
975 public void doRssFailed( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
976 rss(req, rsp, " failed builds", getBuilds().failureOnly());
977 }
978
979 public RunList getBuilds() {
980 return new RunList(this);
981 }
982
983 public BuildTimelineWidget getTimeline() {
984 return new BuildTimelineWidget(getBuilds());
985 }
986
987 private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs) throws IOException, ServletException {
988 RSS.forwardToRss(getDisplayName()+ suffix, getUrl(),
989 runs.newBuilds(), Run.FEED_ADAPTER, req, rsp );
990 }
991
992 public void doRssLatest( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
993 List<Run> lastBuilds = new ArrayList<Run>();
994 for (TopLevelItem item : getItems()) {
995 if (item instanceof Job) {
996 Job job = (Job) item;
997 Run lb = job.getLastBuild();
998 if(lb!=null) lastBuilds.add(lb);
999 }
1000 }
1001 RSS.forwardToRss(getDisplayName()+" last builds only", getUrl(),
1002 lastBuilds, Run.FEED_ADAPTER_LATEST, req, rsp );
1003 }
1004
1005 /**
1006 * Accepts <tt>config.xml</tt> submission, as well as serve it.
1007 */
1008 @WebMethod(name = "config.xml")
1009 public HttpResponse doConfigDotXml(StaplerRequest req) throws IOException {
1010 if (req.getMethod().equals("GET")) {
1011 // read
1012 checkPermission(READ);
1013 return new HttpResponse() {
1014 public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
1015 rsp.setContentType("application/xml");
1016 // pity we don't have a handy way to clone Jenkins.XSTREAM to temp add the omit Field
1017 XStream2 xStream2 = new XStream2();
1018 xStream2.omitField(View.class, "owner");
1019 rsp.getOutputStream().write("<?xml version='1.0' encoding='UTF-8'?>\n".getBytes("UTF-8"));
1020 xStream2.toXML(this, rsp.getOutputStream());
1021 }
1022 };
1023 }
1024 if (req.getMethod().equals("POST")) {
1025 // submission
1026 updateByXml((Source)new StreamSource(req.getReader()));
1027 return HttpResponses.ok();
1028 }
1029
1030 // huh?
1031 return HttpResponses.error(SC_BAD_REQUEST, "Unexpected request method " + req.getMethod());
1032 }
1033
1034 /**
1035 * Updates Job by its XML definition.
1036 */
1037 public void updateByXml(Source source) throws IOException {
1038 checkPermission(CONFIGURE);
1039 StringWriter out = new StringWriter();
1040 try {
1041 // this allows us to use UTF-8 for storing data,
1042 // plus it checks any well-formedness issue in the submitted
1043 // data
1044 Transformer t = TransformerFactory.newInstance()
1045 .newTransformer();
1046 t.transform(source,
1047 new StreamResult(out));
1048 out.close();
1049 } catch (TransformerException e) {
1050 throw new IOException2("Failed to persist configuration.xml", e);
1051 }
1052
1053 // try to reflect the changes by reloading
1054 InputStream in = new BufferedInputStream(new ByteArrayInputStream(out.toString().getBytes("UTF-8")));
1055 try {
1056 Jenkins.XSTREAM.unmarshal(new XppDriver().createReader(in), this);
1057 } catch (StreamException e) {
1058 throw new IOException2("Unable to read",e);
1059 } catch(ConversionException e) {
1060 throw new IOException2("Unable to read",e);
1061 } catch(Error e) {// mostly reflection errors
1062 throw new IOException2("Unable to read",e);
1063 } finally {
1064 in.close();
1065 }
1066 }
1067
1068
1069 /**
1070 * A list of available view types.
1071 * @deprecated as of 1.286
1072 * Use {@link #all()} for read access, and use {@link Extension} for registration.
1073 */
1074 public static final DescriptorList<View> LIST = new DescriptorList<View>(View.class);
1075
1076 /**
1077 * Returns all the registered {@link ViewDescriptor}s.
1078 */
1079 public static DescriptorExtensionList<View,ViewDescriptor> all() {
1080 return Jenkins.getInstance().<View,ViewDescriptor>getDescriptorList(View.class);
1081 }
1082
1083 public static List<ViewDescriptor> allInstantiable() {
1084 List<ViewDescriptor> r = new ArrayList<ViewDescriptor>();
1085 for (ViewDescriptor d : all())
1086 if(d.isInstantiable())
1087 r.add(d);
1088 return r;
1089 }
1090
1091 public static final Comparator<View> SORTER = new Comparator<View>() {
1092 public int compare(View lhs, View rhs) {
1093 return lhs.getViewName().compareTo(rhs.getViewName());
1094 }
1095 };
1096
1097 public static final PermissionGroup PERMISSIONS = new PermissionGroup(View.class,Messages._View_Permissions_Title());
1098 /**
1099 * Permission to create new views.
1100 */
1101 public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._View_CreatePermission_Description(), Permission.CREATE, PermissionScope.ITEM_GROUP);
1102 public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Messages._View_DeletePermission_Description(), Permission.DELETE, PermissionScope.ITEM_GROUP);
1103 public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Messages._View_ConfigurePermission_Description(), Permission.CONFIGURE, PermissionScope.ITEM_GROUP);
1104 public static final Permission READ = new Permission(PERMISSIONS,"Read", Messages._View_ReadPermission_Description(), Permission.READ, PermissionScope.ITEM_GROUP);
1105
1106 // to simplify access from Jelly
1107 public static Permission getItemCreatePermission() {
1108 return Item.CREATE;
1109 }
1110
1111 public static View create(StaplerRequest req, StaplerResponse rsp, ViewGroup owner)
1112 throws FormException, IOException, ServletException {
1113 String requestContentType = req.getContentType();
1114 if(requestContentType==null)
1115 throw new Failure("No Content-Type header set");
1116
1117 boolean isXmlSubmission = requestContentType.startsWith("application/xml") || requestContentType.startsWith("text/xml");
1118
1119 String name = req.getParameter("name");
1120 checkGoodName(name);
1121 if(owner.getView(name)!=null)
1122 throw new FormException(Messages.Hudson_ViewAlreadyExists(name),"name");
1123
1124 String mode = req.getParameter("mode");
1125 if (mode==null || mode.length()==0) {
1126 if(isXmlSubmission) {
1127 View v;
1128 v = createViewFromXML(name, req.getInputStream());
1129 v.owner = owner;
1130 rsp.setStatus(HttpServletResponse.SC_OK);
1131 return v;
1132 } else
1133 throw new FormException(Messages.View_MissingMode(),"mode");
1134 }
1135
1136 // create a view
1137 View v = all().findByName(mode).newInstance(req,req.getSubmittedForm());
1138 v.owner = owner;
1139
1140 // redirect to the config screen
1141 rsp.sendRedirect2(req.getContextPath()+'/'+v.getUrl()+v.getPostConstructLandingPage());
1142
1143 return v;
1144 }
1145
1146 public static View createViewFromXML(String name, InputStream xml) throws IOException {
1147 InputStream in = new BufferedInputStream(xml);
1148 try {
1149 View v = (View) Jenkins.XSTREAM.fromXML(in);
1150 v.name = name;
1151 return v;
1152 } catch(StreamException e) {
1153 throw new IOException2("Unable to read",e);
1154 } catch(ConversionException e) {
1155 throw new IOException2("Unable to read",e);
1156 } catch(Error e) {// mostly reflection errors
1157 throw new IOException2("Unable to read",e);
1158 } finally {
1159 in.close();
1160 }
1161 }
1162
1163 public static class PropertyList extends DescribableList<ViewProperty,ViewPropertyDescriptor> {
1164 private PropertyList(View owner) {
1165 super(owner);
1166 }
1167
1168 public PropertyList() {// needed for XStream deserialization
1169 }
1170
1171 public View getOwner() {
1172 return (View)owner;
1173 }
1174
1175 @Override
1176 protected void onModified() throws IOException {
1177 for (ViewProperty p : this)
1178 p.setView(getOwner());
1179 }
1180 }
1181
1182 /**
1183 * "Job" in "New Job". When a view is used in a context that restricts the child type,
1184 * It might be useful to override this.
1185 */
1186 public static final Message<View> NEW_PRONOUN = new Message<View>();
1187
1188 private final static Logger LOGGER = Logger.getLogger(View.class.getName());
1189 }
1190