forked from jenkinsci/jenkins
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Plugin.java
304 lines (280 loc) · 11.1 KB
/
Plugin.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
*
* 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.
*/
package hudson;
import java.util.concurrent.TimeUnit;
import jenkins.model.Jenkins;
import hudson.model.Descriptor;
import hudson.model.Saveable;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SaveableListener;
import hudson.model.Descriptor.FormException;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.File;
import net.sf.json.JSONObject;
import com.thoughtworks.xstream.XStream;
import hudson.init.Initializer;
import hudson.init.Terminator;
import java.net.URL;
import java.util.Locale;
import java.util.logging.Logger;
import jenkins.model.GlobalConfiguration;
/**
* Base class of Hudson plugin.
*
* <p>
* A plugin may {@linkplain #Plugin derive from this class}, or it may directly define extension
* points annotated with {@link hudson.Extension}. For a list of extension
* points, see <a href="https://jenkins.io/redirect/developer/extension-points">
* https://jenkins.io/redirect/developer/extension-points</a>.
*
* <p>
* One instance of a plugin is created by Hudson, and used as the entry point
* to plugin functionality.
*
* <p>
* A plugin is bound to URL space of Hudson as <tt>${rootURL}/plugin/foo/</tt>,
* where "foo" is taken from your plugin name "foo.jpi". All your web resources
* in src/main/webapp are visible from this URL, and you can also define Jelly
* views against your Plugin class, and those are visible in this URL, too.
*
* <p>
* {@link Plugin} can have an optional <tt>config.jelly</tt> page. If present,
* it will become a part of the system configuration page (http://server/hudson/configure).
* This is convenient for exposing/maintaining configuration that doesn't
* fit any {@link Descriptor}s.
*
* <p>
* Up until Hudson 1.150 or something, subclasses of {@link Plugin} required
* <tt>@plugin</tt> javadoc annotation, but that is no longer a requirement.
*
* @author Kohsuke Kawaguchi
* @since 1.42
*/
public abstract class Plugin implements Saveable {
private static final Logger LOGGER = Logger.getLogger(Plugin.class.getName());
/**
* You do not need to create custom subtypes:
* <ul>
* <li>{@code config.jelly}, {@link #configure(StaplerRequest, JSONObject)}, {@link #load}, and {@link #save}
* can be replaced by {@link GlobalConfiguration}
* <li>{@link #start} and {@link #postInitialize} can be replaced by {@link Initializer} (or {@link ItemListener#onLoaded})
* <li>{@link #stop} can be replaced by {@link Terminator}
* <li>{@link #setServletContext} can be replaced by {@link Jenkins#servletContext}
* </ul>
* Note that every plugin gets a {@link DummyImpl} by default,
* which will still route the URL space, serve {@link #getWrapper}, and so on.
* @deprecated Use more modern APIs rather than subclassing.
*/
@Deprecated
protected Plugin() {}
/**
* Set by the {@link PluginManager}, before the {@link #start()} method is called.
* This points to the {@link PluginWrapper} that wraps
* this {@link Plugin} object.
*/
/*package*/ transient PluginWrapper wrapper;
/**
* Called when a plugin is loaded to make the {@link ServletContext} object available to a plugin.
* This object allows plugins to talk to the surrounding environment.
*
* <p>
* The default implementation is no-op.
*
* @param context
* Always non-null.
*
* @since 1.42
*/
public void setServletContext(ServletContext context) {
}
/**
* Gets the paired {@link PluginWrapper}.
*
* @since 1.426
*/
public PluginWrapper getWrapper() {
return wrapper;
}
/**
* Called to allow plugins to initialize themselves.
*
* <p>
* This method is called after {@link #setServletContext(ServletContext)} is invoked.
* You can also use {@link jenkins.model.Jenkins#getInstance()} to access the singleton hudson instance,
* although the plugin start up happens relatively early in the initialization
* stage and not all the data are loaded in Hudson.
*
* <p>
* If a plugin wants to run an initialization step after all plugins and extension points
* are registered, a good place to do that is {@link #postInitialize()}.
* If a plugin wants to run an initialization step after all the jobs are loaded,
* {@link ItemListener#onLoaded()} is a good place.
*
* @throws Exception
* any exception thrown by the plugin during the initialization will disable plugin.
*
* @since 1.42
* @see ExtensionPoint
* @see #postInitialize()
*/
public void start() throws Exception {
}
/**
* Called after {@link #start()} is called for all the plugins.
*
* @throws Exception
* any exception thrown by the plugin during the initialization will disable plugin.
*/
public void postInitialize() throws Exception {}
/**
* Called to orderly shut down Hudson.
*
* <p>
* This is a good opportunity to clean up resources that plugin started.
* This method will not be invoked if the {@link #start()} failed abnormally.
*
* @throws Exception
* if any exception is thrown, it is simply recorded and shut-down of other
* plugins continue. This is primarily just a convenience feature, so that
* each plugin author doesn't have to worry about catching an exception and
* recording it.
*
* @since 1.42
*/
public void stop() throws Exception {
}
/**
* @since 1.233
* @deprecated as of 1.305 override {@link #configure(StaplerRequest,JSONObject)} instead
*/
@Deprecated
public void configure(JSONObject formData) throws IOException, ServletException, FormException {
}
/**
* Handles the submission for the system configuration.
*
* <p>
* If this class defines <tt>config.jelly</tt> view, be sure to
* override this method and persists the submitted values accordingly.
*
* <p>
* The following is a sample <tt>config.jelly</tt> that you can start yours with:
* <pre>{@code <xmp>
* <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
* <f:section title="Locale">
* <f:entry title="${%Default Language}" help="/plugin/locale/help/default-language.html">
* <f:textbox name="systemLocale" value="${it.systemLocale}" />
* </f:entry>
* </f:section>
* </j:jelly>
* </xmp>}</pre>
*
* <p>
* This allows you to access data as {@code formData.getString("systemLocale")}
*
* <p>
* If you are using this method, you'll likely be interested in
* using {@link #save()} and {@link #load()}.
* @since 1.305
*/
public void configure(StaplerRequest req, JSONObject formData) throws IOException, ServletException, FormException {
configure(formData);
}
/**
* This method serves static resources in the plugin under <tt>hudson/plugin/SHORTNAME</tt>.
*/
public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
String path = req.getRestOfPath();
String pathUC = path.toUpperCase(Locale.ENGLISH);
if (path.isEmpty() || path.contains("..") || path.startsWith(".") || path.contains("%")
|| pathUC.contains("META-INF") || pathUC.contains("WEB-INF")
// ClassicPluginStrategy#explode produce that file to know if a new explosion is required or not
|| pathUC.equals("/.TIMESTAMP2")
) {
LOGGER.warning("rejecting possibly malicious " + req.getRequestURIWithQueryString());
rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
// Stapler routes requests like the "/static/.../foo/bar/zot" to be treated like "/foo/bar/zot"
// and this is used to serve long expiration header, by using Jenkins.VERSION_HASH as "..."
// to create unique URLs. Recognize that and set a long expiration header.
String requestPath = req.getRequestURI().substring(req.getContextPath().length());
boolean staticLink = requestPath.startsWith("/static/");
long expires = staticLink ? TimeUnit.DAYS.toMillis(365) : -1;
// use serveLocalizedFile to support automatic locale selection
rsp.serveLocalizedFile(req, new URL(wrapper.baseResourceURL, '.' + path), expires);
}
//
// Convenience methods for those plugins that persist configuration
//
/**
* Loads serializable fields of this instance from the persisted storage.
*
* <p>
* If there was no previously persisted state, this method is no-op.
*
* @since 1.245
*/
protected void load() throws IOException {
XmlFile xml = getConfigXml();
if(xml.exists())
xml.unmarshal(this);
}
/**
* Saves serializable fields of this instance to the persisted storage.
*
* @since 1.245
*/
public void save() throws IOException {
if(BulkChange.contains(this)) return;
XmlFile config = getConfigXml();
config.write(this);
SaveableListener.fireOnChange(this, config);
}
/**
* Controls the file where {@link #load()} and {@link #save()}
* persists data.
*
* This method can be also overridden if the plugin wants to
* use a custom {@link XStream} instance to persist data.
*
* @since 1.245
*/
protected XmlFile getConfigXml() {
return new XmlFile(Jenkins.XSTREAM,
new File(Jenkins.getInstance().getRootDir(),wrapper.getShortName()+".xml"));
}
/**
* Dummy instance of {@link Plugin} to be used when a plugin didn't
* supply one on its own.
*
* @since 1.321
*/
public static final class DummyImpl extends Plugin {}
}