/* * 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.model; import hudson.Extension; import hudson.ExtensionList; import hudson.ExtensionPoint; import hudson.ProxyConfiguration; import hudson.util.FormValidation; import hudson.util.FormValidation.Kind; import hudson.util.QuotedStringTokenizer; import hudson.util.TextFile; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLEncoder; import java.util.logging.Logger; import jenkins.model.DownloadSettings; import jenkins.model.Jenkins; import jenkins.util.JSONSignatureValidator; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import static hudson.util.TimeUnit2.DAYS; import org.apache.commons.io.IOUtils; /** * Service for plugins to periodically retrieve update data files * (like the one in the update center) through browsers. * *
* Because the retrieval of the file goes through XmlHttpRequest,
* we cannot reliably pass around binary.
*
* @author Kohsuke Kawaguchi
*/
@Extension
public class DownloadService extends PageDecorator {
/**
* Builds up an HTML fragment that starts all the download jobs.
*/
public String generateFragment() {
if (!DownloadSettings.usePostBack()) {
return "";
}
if (neverUpdate) return "";
if (doesNotSupportPostMessage()) return "";
StringBuilder buf = new StringBuilder();
if(Jenkins.getInstance().hasPermission(Jenkins.READ)) {
long now = System.currentTimeMillis();
for (Downloadable d : Downloadable.all()) {
if(d.getDue()
* This mechanism is one of the basis of the update center, which involves fetching
* up-to-date data file.
*
* @since 1.305
*/
public static class Downloadable implements ExtensionPoint {
private final String id;
private final String url;
private final long interval;
private volatile long due=0;
private volatile long lastAttempt=Long.MIN_VALUE;
/**
*
* @param url
* URL relative to {@link UpdateCenter#getDefaultBaseUrl()}.
* So if this string is "foo.json", the ultimate URL will be
* something like "http://updates.jenkins-ci.org/updates/foo.json"
*
* For security and privacy reasons, we don't allow the retrieval
* from random locations.
*/
public Downloadable(String id, String url, long interval) {
this.id = id;
this.url = url;
this.interval = interval;
}
public Downloadable() {
this.id = getClass().getName().replace('$','.');
this.url = this.id+".json";
this.interval = DEFAULT_INTERVAL;
}
/**
* Uses the class name as an ID.
*/
public Downloadable(Class id) {
this(id.getName().replace('$','.'));
}
public Downloadable(String id) {
this(id,id+".json");
}
public Downloadable(String id, String url) {
this(id,url, DEFAULT_INTERVAL);
}
public String getId() {
return id;
}
/**
* URL to download.
*/
public String getUrl() {
return Jenkins.getInstance().getUpdateCenter().getDefaultBaseUrl()+"updates/"+url;
}
/**
* How often do we retrieve the new image?
*
* @return
* number of milliseconds between retrieval.
*/
public long getInterval() {
return interval;
}
/**
* This is where the retrieved file will be stored.
*/
public TextFile getDataFile() {
return new TextFile(new File(Jenkins.getInstance().getRootDir(),"updates/"+id));
}
/**
* When shall we retrieve this file next time?
*/
public long getDue() {
if(due==0)
// if the file doesn't exist, this code should result
// in a very small (but >0) due value, which should trigger
// the retrieval immediately.
due = getDataFile().file.lastModified()+interval;
return due;
}
/**
* Loads the current file into JSON and returns it, or null
* if no data exists.
*/
public JSONObject getData() throws IOException {
TextFile df = getDataFile();
if(df.exists())
try {
return JSONObject.fromObject(df.read());
} catch (JSONException e) {
df.delete(); // if we keep this file, it will cause repeated failures
throw new IOException("Failed to parse "+df+" into JSON",e);
}
return null;
}
/**
* This is where the browser sends us the data.
*/
public void doPostBack(StaplerRequest req, StaplerResponse rsp) throws IOException {
DownloadSettings.checkPostBackAccess();
long dataTimestamp = System.currentTimeMillis();
due = dataTimestamp+getInterval(); // success or fail, don't try too often
String json = IOUtils.toString(req.getInputStream(),"UTF-8");
FormValidation e = load(json, dataTimestamp);
if (e.kind != Kind.OK) {
LOGGER.severe(e.renderHtml());
throw e;
}
rsp.setContentType("text/plain"); // So browser won't try to parse response
}
private FormValidation load(String json, long dataTimestamp) throws IOException {
JSONObject o = JSONObject.fromObject(json);
if (signatureCheck) {
FormValidation e = new JSONSignatureValidator("downloadable '"+id+"'").verifySignature(o);
if (e.kind!= Kind.OK) {
return e;
}
}
TextFile df = getDataFile();
df.write(json);
df.file.setLastModified(dataTimestamp);
LOGGER.info("Obtained the updated data file for "+id);
return FormValidation.ok();
}
@Restricted(NoExternalUse.class)
public FormValidation updateNow() throws IOException {
return load(loadJSONHTML(new URL(getUrl() + ".html?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis());
}
/**
* Returns all the registered {@link Downloadable}s.
*/
public static ExtensionList