/* * 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. * *
* 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 * https://jenkins.io/redirect/developer/extension-points. * *
* One instance of a plugin is created by Hudson, and used as the entry point * to plugin functionality. * *
* A plugin is bound to URL space of Hudson as {@code ${rootURL}/plugin/foo/}, * 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. * *
* {@link Plugin} can have an optional {@code config.jelly} 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. * *
* Up until Hudson 1.150 or something, subclasses of {@link Plugin} required * {@code @plugin} 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: *
* 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. * *
* 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. * *
* 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. * *
* 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. * *
* If this class defines {@code config.jelly} view, be sure to * override this method and persists the submitted values accordingly. * *
* The following is a sample {@code config.jelly} that you can start yours with: *
{@code
*
*
*
*
*
*
*
* }
*
* * This allows you to access data as {@code formData.getString("systemLocale")} * *
* 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 {@code hudson/plugin/SHORTNAME}. */ 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. * *
* 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.get().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 {} }