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

1 /*
2 * The MIT License
3 *
4 * Copyright 2012 Jesse Glick.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25 package jenkins.util;
26
27 import edu.umd.cs.findbugs.annotations.SuppressWarnings;
28 import hudson.model.Computer;
29 import java.util.concurrent.ExecutorService;
30 import java.util.logging.Level;
31 import java.util.logging.Logger;
32 import javax.annotation.Nonnull;
33 import net.sf.json.JSON;
34 import net.sf.json.JSONObject;
35 import org.acegisecurity.context.SecurityContext;
36 import org.acegisecurity.context.SecurityContextHolder;
37 import org.kohsuke.stapler.Stapler;
38 import org.kohsuke.stapler.StaplerRequest;
39 import org.kohsuke.stapler.bind.JavaScriptMethod;
40
41 /**
42 * A helper thread which does some computation in the background and displays incremental results using JavaScript.
43 * This is appropriate when the computation may be slow—too slow to do synchronously within the initial HTTP request—and has no side effects
44 * (since it may be canceled if the user simply browses to another page while it is running).
45 * <ol>
46 * <li>Write a {@code <script>} section defining {@code function display(data)}.
47 * (Call {@code ts_refresh(document.getElementById('someid'))} if using a {@code sortable} table.)
48 * <li>Use {@code <l:progressiveRendering handler="${it.something()}" callback="display"/>} from your
49 * Jelly page to display a progress bar and initialize JavaScript infrastructure.
50 * (The callback attribute can take arbitrary JavaScript expression to be evaluated in the browser
51 * so long as it produces a function object.)
52 * <li>Implement {@code something()} to create an instance of your subclass of {@code ProgressiveRendering}.
53 * <li>Perform your work in {@link #compute}.
54 * <li>Periodically check {@link #canceled}.
55 * <li>As results become available, call {@link #progress}.
56 * <li>Make {@link #data} produce whatever JSON you want to send to the page to be displayed.
57 * </ol>
58 * {@code ui-samples-plugin} demonstrates all this.
59 * @since 1.484
60 */
61 public abstract class ProgressiveRendering {
62
63 private static final Logger LOG = Logger.getLogger(ProgressiveRendering.class.getName());
64 private static final int CANCELED = -1;
65 private static final int ERROR = -2;
66
67 private double status = 0;
68 private long lastNewsTime;
69 /** just for logging */
70 private final String uri;
71 private long start;
72
73 /** Constructor for subclasses. */
74 protected ProgressiveRendering() {
75 StaplerRequest currentRequest = Stapler.getCurrentRequest();
76 uri = currentRequest != null ? currentRequest.getRequestURI() : "?";
77 }
78
79 /**
80 * For internal use.
81 */
82 @SuppressWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
83 public final void start() {
84 final SecurityContext securityContext = SecurityContextHolder.getContext();
85 executorService().submit(new Runnable() {
86 public void run() {
87 lastNewsTime = start = System.currentTimeMillis();
88 SecurityContext orig = SecurityContextHolder.getContext();
89 try {
90 SecurityContextHolder.setContext(securityContext);
91 compute();
92 if (status != CANCELED && status != ERROR) {
93 status = 1;
94 }
95 } catch (Exception x) {
96 LOG.log(Level.WARNING, "failed to compute " + uri, x);
97 status = ERROR;
98 } finally {
99 SecurityContextHolder.setContext(orig);
100 LOG.log(Level.FINE, "{0} finished in {1}msec with status {2}", new Object[] {uri, System.currentTimeMillis() - start, status});
101 }
102 }
103 });
104 }
105
106 /**
107 * Actually do the work.
108 * <p>The security context will be that in effect when the web request was made.
109 * @throws Exception whenever you like; the progress bar will indicate that an error occurred but details go to the log only
110 */
111 protected abstract void compute() throws Exception;
112
113 /**
114 * Provide current data to the web page for display.
115 * <p>While this could be an aggregate of everything that has been computed so far,
116 * more likely you want to supply only that data that is new since the last call
117 * (maybe just {@code {}} or {@code []}),
118 * so that the page can incrementally update bits of HTML rather than refreshing everything.
119 * <p>You may want to make your implementation {@code synchronized}, so that it
120 * can track what was sent on a previous call, in which case any code running in
121 * {@link #compute} which modifies these fields should also <em>temporarily</em> be synchronized
122 * on the same monitor such as {@code this}.
123 * @return any JSON data you like
124 */
125 protected abstract @Nonnull JSON data();
126
127 /**
128 * Indicate what portion of the work has been done.
129 * (Once {@link #compute} returns, the work is assumed to be complete regardless of this method.)
130 * @param completedFraction estimated portion of work now done, from 0 (~ 0%) to 1 (~ 100%)
131 */
132 protected final void progress(double completedFraction) {
133 if (completedFraction < 0 || completedFraction > 1) {
134 throw new IllegalArgumentException(completedFraction + " should be in [0,1]");
135 }
136 status = completedFraction;
137 }
138
139 /**
140 * Checks whether the task has been canceled.
141 * If the rendering page fails to send a heartbeat within a certain amount of time,
142 * the user is assumed to have moved on.
143 * Therefore {@link #compute} should periodically say:
144 * {@code if (canceled()) return;}
145 * @return true if user seems to have abandoned us, false if we should still run
146 */
147 protected final boolean canceled() {
148 if (status == ERROR) {
149 return true; // recent call to data() failed
150 }
151 long now = System.currentTimeMillis();
152 long elapsed = now - lastNewsTime;
153 if (elapsed > timeout()) {
154 status = CANCELED;
155 LOG.log(Level.FINE, "{0} canceled due to {1}msec inactivity after {2}msec", new Object[] {uri, elapsed, now - start});
156 return true;
157 } else {
158 return false;
159 }
160 }
161
162 /**
163 * For internal use.
164 */
165 @JavaScriptMethod public final JSONObject news() {
166 lastNewsTime = System.currentTimeMillis();
167 JSONObject r = new JSONObject();
168 try {
169 r.put("data", data());
170 } catch (RuntimeException x) {
171 LOG.log(Level.WARNING, "failed to update " + uri, x);
172 status = ERROR;
173 }
174 r.put("status", status == 1 ? "done" : status == CANCELED ? "canceled" : status == ERROR ? "error" : status);
175 lastNewsTime = System.currentTimeMillis();
176 LOG.log(Level.FINER, "news from {0}", uri);
177 return r;
178 }
179
180 /**
181 * May be overridden to provide an alternate executor service.
182 * @return by default, {@link Computer#threadPoolForRemoting}
183 */
184 protected ExecutorService executorService() {
185 return Computer.threadPoolForRemoting;
186 }
187
188 /**
189 * May be overridden to control the inactivity timeout.
190 * If no request from the browser is received within this time,
191 * the next call to {@link #canceled} will be true.
192 * @return timeout in milliseconds; by default, 15000 (~ 15 seconds)
193 */
194 protected long timeout() {
195 return 15000;
196 }
197
198 }
199