Files
jenkins/core/src/main/java/hudson/Functions.java
T
Daniel Beck 2a03dda885 Reference redirectors and jenkins.io as much as possible (#2756)
* Reference redirectors and jenkins.io as much as possible

* Something went wrong, so this is a troubleshooting URL

* More specific redirect URLs
2017-02-25 19:56:01 +01:00

2047 lines
73 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* The MIT License
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Yahoo! Inc., Stephen Connolly, Tom Huybrechts, Alan Harder, Manufacture
* Francaise des Pneumatiques Michelin, Romain Seguy
*
* 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 hudson.model.Slave;
import jenkins.util.SystemProperties;
import hudson.cli.CLICommand;
import hudson.console.ConsoleAnnotationDescriptor;
import hudson.console.ConsoleAnnotatorFactory;
import hudson.init.InitMilestone;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.DescriptorVisibilityFilter;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Items;
import hudson.model.JDK;
import hudson.model.Job;
import hudson.model.JobPropertyDescriptor;
import hudson.model.ModelObject;
import hudson.model.Node;
import hudson.model.PageDecorator;
import hudson.model.PaneStatusProperties;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterDefinition.ParameterDescriptor;
import hudson.model.Run;
import hudson.model.TopLevelItem;
import hudson.model.User;
import hudson.model.View;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.search.SearchableModelObject;
import hudson.security.AccessControlled;
import hudson.security.AuthorizationStrategy;
import hudson.security.GlobalSecurityConfiguration;
import hudson.security.Permission;
import hudson.security.SecurityRealm;
import hudson.security.captcha.CaptchaSupport;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.Cloud;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.RetentionStrategy;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrappers;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.tasks.UserAvatarResolver;
import hudson.util.Area;
import hudson.util.FormValidation.CheckMethod;
import hudson.util.HudsonIsLoading;
import hudson.util.HudsonIsRestarting;
import hudson.util.Iterators;
import hudson.util.jna.GNUCLibrary;
import hudson.util.Secret;
import hudson.views.MyViewsTabBar;
import hudson.views.ViewsTabBar;
import hudson.widgets.RenderOnDemandClosure;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyTagException;
import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jexl.parser.ASTSizeFunction;
import org.apache.commons.jexl.util.Introspector;
import org.apache.commons.lang.StringUtils;
import org.jenkins.ui.icon.IconSet;
import org.jvnet.tiger_types.Types;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.jelly.InternationalizedStringExpression.RawHtmlArgument;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import hudson.model.PasswordParameterDefinition;
import hudson.util.RunList;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.apache.commons.io.IOUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* Utility functions used in views.
*
* <p>
* An instance of this class is created for each request and made accessible
* from view pages via the variable 'h' (h stands for Hudson.)
*
* @author Kohsuke Kawaguchi
*/
@SuppressWarnings("rawtypes")
public class Functions {
private static final AtomicLong iota = new AtomicLong();
public Functions() {
}
/**
* Generates an unique ID.
*/
public String generateId() {
return "id" + iota.getAndIncrement();
}
public static boolean isModel(Object o) {
return o instanceof ModelObject;
}
public static boolean isModelWithContextMenu(Object o) {
return o instanceof ModelObjectWithContextMenu;
}
public static boolean isModelWithChildren(Object o) {
return o instanceof ModelObjectWithChildren;
}
@Deprecated
public static boolean isMatrixProject(Object o) {
return o != null && o.getClass().getName().equals("hudson.matrix.MatrixProject");
}
public static String xsDate(Calendar cal) {
return Util.XS_DATETIME_FORMATTER.format(cal.getTime());
}
public static String rfc822Date(Calendar cal) {
return Util.RFC822_DATETIME_FORMATTER.format(cal.getTime());
}
/**
* During Jenkins start-up, before {@link InitMilestone#PLUGINS_STARTED} the extensions lists will be empty
* and they are not guaranteed to be fully populated until after {@link InitMilestone#EXTENSIONS_AUGMENTED},
* similarly, during termination after {@link Jenkins#isTerminating()} is set, it is no longer safe to access
* the extensions lists.
* If you attempt to access the extensions list from a UI thread while the extensions are being loaded you will
* hit a big honking great monitor lock that will block until the effective extension list has been determined
* (as if a plugin fails to start, all of the failed plugin's extensions and any dependent plugins' extensions
* will have to be evicted from the list of extensions. In practical terms this only affects the
* "Jenkins is loading" screen, but as that screen uses the generic layouts we provide this utility method
* so that the generic layouts can avoid iterating extension lists while Jenkins is starting up.
* If you attempt to access the extensions list from a UI thread while Jenkins is being shut down, the extensions
* themselves may no longer be in a valid state and could attempt to revive themselves and block termination.
* In actual terms the termination only affects those views required to render {@link HudsonIsRestarting}'s
* {@code index.jelly} which is the same set as the {@link HudsonIsLoading} pages so it makes sense to
* use both checks here.
*
* @return {@code true} if the extensions lists have been populated.
* @since 1.607
*/
public static boolean isExtensionsAvailable() {
final Jenkins jenkins = Jenkins.getInstanceOrNull();
return jenkins != null && jenkins.getInitLevel().compareTo(InitMilestone.EXTENSIONS_AUGMENTED) >= 0
&& !jenkins.isTerminating();
}
public static void initPageVariables(JellyContext context) {
StaplerRequest currentRequest = Stapler.getCurrentRequest();
String rootURL = currentRequest.getContextPath();
Functions h = new Functions();
context.setVariable("h", h);
// The path starts with a "/" character but does not end with a "/" character.
context.setVariable("rootURL", rootURL);
/*
load static resources from the path dedicated to a specific version.
This "/static/VERSION/abc/def.ghi" path is interpreted by stapler to be
the same thing as "/abc/def.ghi", but this avoids the stale cache
problem when the user upgrades to new Jenkins. Stapler also sets a long
future expiration dates for such static resources.
see https://wiki.jenkins-ci.org/display/JENKINS/Hyperlinks+in+HTML
*/
context.setVariable("resURL",rootURL+getResourcePath());
context.setVariable("imagesURL",rootURL+getResourcePath()+"/images");
context.setVariable("userAgent", currentRequest.getHeader("User-Agent"));
IconSet.initPageVariables(context);
}
/**
* Given {@code c=MyList (extends ArrayList<Foo>), base=List}, compute the parameterization of 'base'
* that's assignable from 'c' (in this case {@code List<Foo>}), and return its n-th type parameter
* (n=0 would return {@code Foo}).
*
* <p>
* This method is useful for doing type arithmetic.
*
* @throws AssertionError
* if c' is not parameterized.
*/
public static <B> Class getTypeParameter(Class<? extends B> c, Class<B> base, int n) {
Type parameterization = Types.getBaseClass(c,base);
if (parameterization instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) parameterization;
return Types.erasure(Types.getTypeArgument(pt,n));
} else {
throw new AssertionError(c+" doesn't properly parameterize "+base);
}
}
public JDK.DescriptorImpl getJDKDescriptor() {
return Jenkins.getInstance().getDescriptorByType(JDK.DescriptorImpl.class);
}
/**
* Prints the integer as a string that represents difference,
* like "-5", "+/-0", "+3".
*/
public static String getDiffString(int i) {
if(i==0) return "±0";
String s = Integer.toString(i);
if(i>0) return "+"+s;
else return s;
}
/**
* {@link #getDiffString(int)} that doesn't show anything for +/-0
*/
public static String getDiffString2(int i) {
if(i==0) return "";
String s = Integer.toString(i);
if(i>0) return "+"+s;
else return s;
}
/**
* {@link #getDiffString2(int)} that puts the result into prefix and suffix
* if there's something to print
*/
public static String getDiffString2(String prefix, int i, String suffix) {
if(i==0) return "";
String s = Integer.toString(i);
if(i>0) return prefix+"+"+s+suffix;
else return prefix+s+suffix;
}
/**
* Adds the proper suffix.
*/
public static String addSuffix(int n, String singular, String plural) {
StringBuilder buf = new StringBuilder();
buf.append(n).append(' ');
if(n==1)
buf.append(singular);
else
buf.append(plural);
return buf.toString();
}
public static RunUrl decompose(StaplerRequest req) {
List<Ancestor> ancestors = req.getAncestors();
// find the first and last Run instances
Ancestor f=null,l=null;
for (Ancestor anc : ancestors) {
if(anc.getObject() instanceof Run) {
if(f==null) f=anc;
l=anc;
}
}
if(l==null) return null; // there was no Run object
String head = f.getPrev().getUrl()+'/';
String base = l.getUrl();
String reqUri = req.getOriginalRequestURI();
// Find "rest" or URI by removing N path components.
// Not using reqUri.substring(f.getUrl().length()) to avoid mismatches due to
// url-encoding or extra slashes. Former may occur in Tomcat (despite the spec saying
// this string is not decoded, Tomcat apparently decodes this string. You see ' '
// instead of '%20', which is what the browser has sent), latter may occur in some
// proxy or URL-rewriting setups where extra slashes are inadvertently added.
String furl = f.getUrl();
int slashCount = 0;
// Count components in ancestor URL
for (int i = furl.indexOf('/'); i >= 0; i = furl.indexOf('/', i + 1)) slashCount++;
// Remove that many from request URL, ignoring extra slashes
String rest = reqUri.replaceFirst("(?:/+[^/]*){" + slashCount + "}", "");
return new RunUrl( (Run) f.getObject(), head, base, rest);
}
/**
* If we know the user's screen resolution, return it. Otherwise null.
* @since 1.213
*/
public static Area getScreenResolution() {
Cookie res = Functions.getCookie(Stapler.getCurrentRequest(),"screenResolution");
if(res!=null)
return Area.parse(res.getValue());
return null;
}
/**
* URL decomposed for easier computation of relevant URLs.
*
* <p>
* The decomposed URL will be of the form:
* <pre>
* aaaaaa/524/bbbbb/cccc
* -head-| N |---rest---
* ----- base -----|
* </pre>
*
* <p>
* The head portion is the part of the URL from the {@link jenkins.model.Jenkins}
* object to the first {@link Run} subtype. When "next/prev build"
* is chosen, this part remains intact.
*
* <p>
* The <tt>524</tt> is the path from {@link Job} to {@link Run}.
*
* <p>
* The <tt>bbb</tt> portion is the path after that till the last
* {@link Run} subtype. The <tt>ccc</tt> portion is the part
* after that.
*/
public static final class RunUrl {
private final String head, base, rest;
private final Run run;
public RunUrl(Run run, String head, String base, String rest) {
this.run = run;
this.head = head;
this.base = base;
this.rest = rest;
}
public String getBaseUrl() {
return base;
}
/**
* Returns the same page in the next build.
*/
public String getNextBuildUrl() {
return getUrl(run.getNextBuild());
}
/**
* Returns the same page in the previous build.
*/
public String getPreviousBuildUrl() {
return getUrl(run.getPreviousBuild());
}
private String getUrl(Run n) {
if(n ==null)
return null;
else {
return head+n.getNumber()+rest;
}
}
}
public static Node.Mode[] getNodeModes() {
return Node.Mode.values();
}
public static String getProjectListString(List<AbstractProject> projects) {
return Items.toNameList(projects);
}
/**
* @deprecated as of 1.294
* JEXL now supports the real ternary operator "x?y:z", so this work around
* is no longer necessary.
*/
@Deprecated
public static Object ifThenElse(boolean cond, Object thenValue, Object elseValue) {
return cond ? thenValue : elseValue;
}
public static String appendIfNotNull(String text, String suffix, String nullText) {
return text == null ? nullText : text + suffix;
}
public static Map getSystemProperties() {
return new TreeMap<Object,Object>(System.getProperties());
}
public static Map getEnvVars() {
return new TreeMap<String,String>(EnvVars.masterEnvVars);
}
public static boolean isWindows() {
return File.pathSeparatorChar==';';
}
public static boolean isGlibcSupported() {
try {
GNUCLibrary.LIBC.getpid();
return true;
} catch(Throwable t) {
return false;
}
}
public static List<LogRecord> getLogRecords() {
return Jenkins.logRecords;
}
public static String printLogRecord(LogRecord r) {
return formatter.format(r);
}
@Restricted(NoExternalUse.class)
public static String[] printLogRecordHtml(LogRecord r, LogRecord prior) {
String[] oldParts = prior == null ? new String[4] : logRecordPreformat(prior);
String[] newParts = logRecordPreformat(r);
for (int i = 0; i < /* not 4 */3; i++) {
newParts[i] = "<span class='" + (newParts[i].equals(oldParts[i]) ? "logrecord-metadata-old" : "logrecord-metadata-new") + "'>" + newParts[i] + "</span>";
}
newParts[3] = Util.xmlEscape(newParts[3]);
return newParts;
}
/**
* Partially formats a log record.
* @return date, source, level, message+thrown
* @see SimpleFormatter#format(LogRecord)
*/
private static String[] logRecordPreformat(LogRecord r) {
String source;
if (r.getSourceClassName() == null) {
source = r.getLoggerName();
} else {
if (r.getSourceMethodName() == null) {
source = r.getSourceClassName();
} else {
source = r.getSourceClassName() + " " + r.getSourceMethodName();
}
}
String message = new SimpleFormatter().formatMessage(r) + "\n";
Throwable x = r.getThrown();
return new String[] {
String.format("%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp", new Date(r.getMillis())),
source,
r.getLevel().getLocalizedName(),
x == null ? message : message + printThrowable(x) + "\n"
};
}
/**
* Reverses a collection so that it can be easily walked in reverse order.
* @since 1.525
*/
public static <T> Iterable<T> reverse(Collection<T> collection) {
List<T> list = new ArrayList<T>(collection);
Collections.reverse(list);
return list;
}
public static Cookie getCookie(HttpServletRequest req,String name) {
Cookie[] cookies = req.getCookies();
if(cookies!=null) {
for (Cookie cookie : cookies) {
if(cookie.getName().equals(name)) {
return cookie;
}
}
}
return null;
}
public static String getCookie(HttpServletRequest req,String name, String defaultValue) {
Cookie c = getCookie(req, name);
if(c==null || c.getValue()==null) return defaultValue;
return c.getValue();
}
private static final Pattern ICON_SIZE = Pattern.compile("\\d+x\\d+");
@Restricted(NoExternalUse.class)
public static String validateIconSize(String iconSize) throws SecurityException {
if (!ICON_SIZE.matcher(iconSize).matches()) {
throw new SecurityException("invalid iconSize");
}
return iconSize;
}
/**
* Gets the suffix to use for YUI JavaScript.
*/
public static String getYuiSuffix() {
return DEBUG_YUI ? "debug" : "min";
}
/**
* Set to true if you need to use the debug version of YUI.
*/
public static boolean DEBUG_YUI = SystemProperties.getBoolean("debug.YUI");
/**
* Creates a sub map by using the given range (both ends inclusive).
*/
public static <V> SortedMap<Integer,V> filter(SortedMap<Integer,V> map, String from, String to) {
if(from==null && to==null) return map;
if(to==null)
return map.headMap(Integer.parseInt(from)-1);
if(from==null)
return map.tailMap(Integer.parseInt(to));
return map.subMap(Integer.parseInt(to),Integer.parseInt(from)-1);
}
/**
* Creates a sub map by using the given range (upper end inclusive).
*/
@Restricted(NoExternalUse.class)
public static <V> SortedMap<Integer,V> filterExcludingFrom(SortedMap<Integer,V> map, String from, String to) {
if(from==null && to==null) return map;
if(to==null)
return map.headMap(Integer.parseInt(from));
if(from==null)
return map.tailMap(Integer.parseInt(to));
return map.subMap(Integer.parseInt(to),Integer.parseInt(from));
}
private static final SimpleFormatter formatter = new SimpleFormatter();
/**
* Used by <tt>layout.jelly</tt> to control the auto refresh behavior.
*
* @param noAutoRefresh
* On certain pages, like a page with forms, will have annoying interference
* with auto refresh. On those pages, disable auto-refresh.
*/
public static void configureAutoRefresh(HttpServletRequest request, HttpServletResponse response, boolean noAutoRefresh) {
if(noAutoRefresh)
return;
String param = request.getParameter("auto_refresh");
boolean refresh = isAutoRefresh(request);
if (param != null) {
refresh = Boolean.parseBoolean(param);
Cookie c = new Cookie("hudson_auto_refresh", Boolean.toString(refresh));
// Need to set path or it will not stick from e.g. a project page to the dashboard.
// Using request.getContextPath() might work but it seems simpler to just use the hudson_ prefix
// to avoid conflicts with any other web apps that might be on the same machine.
c.setPath("/");
c.setMaxAge(60*60*24*30); // persist it roughly for a month
response.addCookie(c);
}
if (refresh) {
response.addHeader("Refresh", SystemProperties.getString("hudson.Functions.autoRefreshSeconds", "10"));
}
}
public static boolean isAutoRefresh(HttpServletRequest request) {
String param = request.getParameter("auto_refresh");
if (param != null) {
return Boolean.parseBoolean(param);
}
Cookie[] cookies = request.getCookies();
if(cookies==null)
return false; // when API design messes it up, we all suffer
for (Cookie c : cookies) {
if (c.getName().equals("hudson_auto_refresh")) {
return Boolean.parseBoolean(c.getValue());
}
}
return false;
}
public static boolean isCollapsed(String paneId) {
return PaneStatusProperties.forCurrentUser().isCollapsed(paneId);
}
/**
* Finds the given object in the ancestor list and returns its URL.
* This is used to determine the "current" URL assigned to the given object,
* so that one can compute relative URLs from it.
*/
public static String getNearestAncestorUrl(StaplerRequest req,Object it) {
List list = req.getAncestors();
for( int i=list.size()-1; i>=0; i-- ) {
Ancestor anc = (Ancestor) list.get(i);
if(anc.getObject()==it)
return anc.getUrl();
}
return null;
}
/**
* Finds the inner-most {@link SearchableModelObject} in scope.
*/
public static String getSearchURL() {
List list = Stapler.getCurrentRequest().getAncestors();
for( int i=list.size()-1; i>=0; i-- ) {
Ancestor anc = (Ancestor) list.get(i);
if(anc.getObject() instanceof SearchableModelObject)
return anc.getUrl()+"/search/";
}
return null;
}
public static String appendSpaceIfNotNull(String n) {
if(n==null) return null;
else return n+' ';
}
/**
* One nbsp per 10 pixels in given size, which may be a plain number or "NxN"
* (like an iconSize). Useful in a sortable table heading.
*/
public static String nbspIndent(String size) {
int i = size.indexOf('x');
i = Integer.parseInt(i > 0 ? size.substring(0, i) : size) / 10;
StringBuilder buf = new StringBuilder(30);
for (int j = 0; j < i; j++)
buf.append("&nbsp;");
return buf.toString();
}
public static String getWin32ErrorMessage(IOException e) {
return Util.getWin32ErrorMessage(e);
}
public static boolean isMultiline(String s) {
if(s==null) return false;
return s.indexOf('\r')>=0 || s.indexOf('\n')>=0;
}
public static String encode(String s) {
return Util.encode(s);
}
public static String escape(String s) {
return Util.escape(s);
}
public static String xmlEscape(String s) {
return Util.xmlEscape(s);
}
public static String xmlUnescape(String s) {
return s.replace("&lt;","<").replace("&gt;",">").replace("&amp;","&");
}
public static String htmlAttributeEscape(String text) {
StringBuilder buf = new StringBuilder(text.length()+64);
for( int i=0; i<text.length(); i++ ) {
char ch = text.charAt(i);
if(ch=='<')
buf.append("&lt;");
else
if(ch=='>')
buf.append("&gt;");
else
if(ch=='&')
buf.append("&amp;");
else
if(ch=='"')
buf.append("&quot;");
else
if(ch=='\'')
buf.append("&#39;");
else
buf.append(ch);
}
return buf.toString();
}
public static void checkPermission(Permission permission) throws IOException, ServletException {
checkPermission(Jenkins.getInstance(),permission);
}
public static void checkPermission(AccessControlled object, Permission permission) throws IOException, ServletException {
if (permission != null) {
object.checkPermission(permission);
}
}
/**
* This version is so that the 'checkPermission' on <tt>layout.jelly</tt>
* degrades gracefully if "it" is not an {@link AccessControlled} object.
* Otherwise it will perform no check and that problem is hard to notice.
*/
public static void checkPermission(Object object, Permission permission) throws IOException, ServletException {
if (permission == null)
return;
if (object instanceof AccessControlled)
checkPermission((AccessControlled) object,permission);
else {
List<Ancestor> ancs = Stapler.getCurrentRequest().getAncestors();
for(Ancestor anc : Iterators.reverse(ancs)) {
Object o = anc.getObject();
if (o instanceof AccessControlled) {
checkPermission((AccessControlled) o,permission);
return;
}
}
checkPermission(Jenkins.getInstance(),permission);
}
}
/**
* Returns true if the current user has the given permission.
*
* @param permission
* If null, returns true. This defaulting is convenient in making the use of this method terse.
*/
public static boolean hasPermission(Permission permission) throws IOException, ServletException {
return hasPermission(Jenkins.getInstance(),permission);
}
/**
* This version is so that the 'hasPermission' can degrade gracefully
* if "it" is not an {@link AccessControlled} object.
*/
public static boolean hasPermission(Object object, Permission permission) throws IOException, ServletException {
if (permission == null)
return true;
if (object instanceof AccessControlled)
return ((AccessControlled)object).hasPermission(permission);
else {
List<Ancestor> ancs = Stapler.getCurrentRequest().getAncestors();
for(Ancestor anc : Iterators.reverse(ancs)) {
Object o = anc.getObject();
if (o instanceof AccessControlled) {
return ((AccessControlled)o).hasPermission(permission);
}
}
return Jenkins.getInstance().hasPermission(permission);
}
}
public static void adminCheck(StaplerRequest req, StaplerResponse rsp, Object required, Permission permission) throws IOException, ServletException {
// this is legacy --- all views should be eventually converted to
// the permission based model.
if(required!=null && !Hudson.adminCheck(req, rsp)) {
// check failed. commit the FORBIDDEN response, then abort.
rsp.setStatus(HttpServletResponse.SC_FORBIDDEN);
rsp.getOutputStream().close();
throw new ServletException("Unauthorized access");
}
// make sure the user owns the necessary permission to access this page.
if(permission!=null)
checkPermission(permission);
}
/**
* Infers the hudson installation URL from the given request.
*/
public static String inferHudsonURL(StaplerRequest req) {
String rootUrl = Jenkins.getInstance().getRootUrl();
if(rootUrl !=null)
// prefer the one explicitly configured, to work with load-balancer, frontend, etc.
return rootUrl;
StringBuilder buf = new StringBuilder();
buf.append(req.getScheme()).append("://");
buf.append(req.getServerName());
if(! (req.getScheme().equals("http") && req.getLocalPort()==80 || req.getScheme().equals("https") && req.getLocalPort()==443))
buf.append(':').append(req.getLocalPort());
buf.append(req.getContextPath()).append('/');
return buf.toString();
}
/**
* Returns the link to be displayed in the footer of the UI.
*/
public static String getFooterURL() {
if(footerURL == null) {
footerURL = SystemProperties.getString("hudson.footerURL");
if(StringUtils.isBlank(footerURL)) {
footerURL = "https://jenkins.io/";
}
}
return footerURL;
}
private static String footerURL = null;
public static List<JobPropertyDescriptor> getJobPropertyDescriptors(Class<? extends Job> clazz) {
return JobPropertyDescriptor.getPropertyDescriptors(clazz);
}
public static List<JobPropertyDescriptor> getJobPropertyDescriptors(Job job) {
return DescriptorVisibilityFilter.apply(job, JobPropertyDescriptor.getPropertyDescriptors(job.getClass()));
}
public static List<Descriptor<BuildWrapper>> getBuildWrapperDescriptors(AbstractProject<?,?> project) {
return BuildWrappers.getFor(project);
}
public static List<Descriptor<SecurityRealm>> getSecurityRealmDescriptors() {
return SecurityRealm.all();
}
public static List<Descriptor<AuthorizationStrategy>> getAuthorizationStrategyDescriptors() {
return AuthorizationStrategy.all();
}
public static List<Descriptor<Builder>> getBuilderDescriptors(AbstractProject<?,?> project) {
return BuildStepDescriptor.filter(Builder.all(), project.getClass());
}
public static List<Descriptor<Publisher>> getPublisherDescriptors(AbstractProject<?,?> project) {
return BuildStepDescriptor.filter(Publisher.all(), project.getClass());
}
public static List<SCMDescriptor<?>> getSCMDescriptors(AbstractProject<?,?> project) {
return SCM._for(project);
}
/**
* @since 2.12
* @deprecated replaced by {@link Slave.SlaveDescriptor#computerLauncherDescriptors(Slave)}
*/
@Deprecated
@Restricted(DoNotUse.class)
@RestrictedSince("2.12")
public static List<Descriptor<ComputerLauncher>> getComputerLauncherDescriptors() {
return Jenkins.getInstance().<ComputerLauncher,Descriptor<ComputerLauncher>>getDescriptorList(ComputerLauncher.class);
}
/**
* @since 2.12
* @deprecated replaced by {@link Slave.SlaveDescriptor#retentionStrategyDescriptors(Slave)}
*/
@Deprecated
@Restricted(DoNotUse.class)
@RestrictedSince("2.12")
public static List<Descriptor<RetentionStrategy<?>>> getRetentionStrategyDescriptors() {
return RetentionStrategy.all();
}
public static List<ParameterDescriptor> getParameterDescriptors() {
return ParameterDefinition.all();
}
public static List<Descriptor<CaptchaSupport>> getCaptchaSupportDescriptors() {
return CaptchaSupport.all();
}
public static List<Descriptor<ViewsTabBar>> getViewsTabBarDescriptors() {
return ViewsTabBar.all();
}
public static List<Descriptor<MyViewsTabBar>> getMyViewsTabBarDescriptors() {
return MyViewsTabBar.all();
}
/**
* @deprecated replaced by {@link Slave.SlaveDescriptor#nodePropertyDescriptors(Slave)}
* @since 2.12
*/
@Deprecated
@Restricted(DoNotUse.class)
@RestrictedSince("2.12")
public static List<NodePropertyDescriptor> getNodePropertyDescriptors(Class<? extends Node> clazz) {
List<NodePropertyDescriptor> result = new ArrayList<NodePropertyDescriptor>();
Collection<NodePropertyDescriptor> list = (Collection) Jenkins.getInstance().getDescriptorList(NodeProperty.class);
for (NodePropertyDescriptor npd : list) {
if (npd.isApplicable(clazz)) {
result.add(npd);
}
}
return result;
}
/**
* Returns those node properties which can be configured as global node properties.
*
* @since 1.520
*/
public static List<NodePropertyDescriptor> getGlobalNodePropertyDescriptors() {
List<NodePropertyDescriptor> result = new ArrayList<NodePropertyDescriptor>();
Collection<NodePropertyDescriptor> list = (Collection) Jenkins.getInstance().getDescriptorList(NodeProperty.class);
for (NodePropertyDescriptor npd : list) {
if (npd.isApplicableAsGlobal()) {
result.add(npd);
}
}
return result;
}
/**
* Gets all the descriptors sorted by their inheritance tree of {@link Describable}
* so that descriptors of similar types come nearby.
*
* <p>
* We sort them by {@link Extension#ordinal()} but only for {@link GlobalConfiguration}s,
* as the value is normally used to compare similar kinds of extensions, and we needed
* {@link GlobalConfiguration}s to be able to position themselves in a layer above.
* This however creates some asymmetry between regular {@link Descriptor}s and {@link GlobalConfiguration}s.
* Perhaps it is better to introduce another annotation element? But then,
* extensions shouldn't normally concern themselves about ordering too much, and the only reason
* we needed this for {@link GlobalConfiguration}s are for backward compatibility.
*
* @param predicate
* Filter the descriptors based on {@link GlobalConfigurationCategory}
* @since 1.494
*/
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfig(Predicate<GlobalConfigurationCategory> predicate) {
ExtensionList<Descriptor> exts = ExtensionList.lookup(Descriptor.class);
List<Tag> r = new ArrayList<Tag>(exts.size());
for (ExtensionComponent<Descriptor> c : exts.getComponents()) {
Descriptor d = c.getInstance();
if (d.getGlobalConfigPage()==null) continue;
if (predicate.apply(d.getCategory())) {
r.add(new Tag(c.ordinal(), d));
}
}
Collections.sort(r);
List<Descriptor> answer = new ArrayList<Descriptor>(r.size());
for (Tag d : r) answer.add(d.d);
return DescriptorVisibilityFilter.apply(Jenkins.getInstance(),answer);
}
/**
* Like {@link #getSortedDescriptorsForGlobalConfig(Predicate)} but with a constant truth predicate, to include all descriptors.
*/
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfig() {
return getSortedDescriptorsForGlobalConfig(Predicates.<GlobalConfigurationCategory>alwaysTrue());
}
/**
* @deprecated This is rather meaningless.
*/
@Deprecated
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfigNoSecurity() {
return getSortedDescriptorsForGlobalConfig(Predicates.not(GlobalSecurityConfiguration.FILTER));
}
/**
* Like {@link #getSortedDescriptorsForGlobalConfig(Predicate)} but for unclassified descriptors only.
* @since 1.506
*/
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfigUnclassified() {
return getSortedDescriptorsForGlobalConfig(new Predicate<GlobalConfigurationCategory>() {
public boolean apply(GlobalConfigurationCategory cat) {
return cat instanceof GlobalConfigurationCategory.Unclassified;
}
});
}
private static class Tag implements Comparable<Tag> {
double ordinal;
String hierarchy;
Descriptor d;
Tag(double ordinal, Descriptor d) {
this.ordinal = ordinal;
this.d = d;
this.hierarchy = buildSuperclassHierarchy(d.clazz, new StringBuilder()).toString();
}
private StringBuilder buildSuperclassHierarchy(Class c, StringBuilder buf) {
Class sc = c.getSuperclass();
if (sc!=null) buildSuperclassHierarchy(sc,buf).append(':');
return buf.append(c.getName());
}
public int compareTo(Tag that) {
int r = Double.compare(this.ordinal, that.ordinal);
if (r!=0) return -r; // descending for ordinal
return this.hierarchy.compareTo(that.hierarchy);
}
}
/**
* Computes the path to the icon of the given action
* from the context path.
*/
public static String getIconFilePath(Action a) {
String name = a.getIconFileName();
if (name==null) return null;
if (name.startsWith("/"))
return name.substring(1);
else
return "images/24x24/"+name;
}
/**
* Works like JSTL build-in size(x) function,
* but handle null gracefully.
*/
public static int size2(Object o) throws Exception {
if(o==null) return 0;
return ASTSizeFunction.sizeOf(o,Introspector.getUberspect());
}
/**
* Computes the relative path from the current page to the given item.
*/
public static String getRelativeLinkTo(Item p) {
Map<Object,String> ancestors = new HashMap<Object,String>();
View view=null;
StaplerRequest request = Stapler.getCurrentRequest();
for( Ancestor a : request.getAncestors() ) {
ancestors.put(a.getObject(),a.getRelativePath());
if(a.getObject() instanceof View)
view = (View) a.getObject();
}
String path = ancestors.get(p);
if(path!=null) {
return normalizeURI(path + '/');
}
Item i=p;
String url = "";
while(true) {
ItemGroup ig = i.getParent();
url = i.getShortUrl()+url;
if(ig== Jenkins.getInstance() || (view != null && ig == view.getOwnerItemGroup())) {
assert i instanceof TopLevelItem;
if (view != null) {
// assume p and the current page belong to the same view, so return a relative path
// (even if they did not, View.getItem does not by default verify ownership)
return normalizeURI(ancestors.get(view)+'/'+url);
} else {
// otherwise return a path from the root Hudson
return normalizeURI(request.getContextPath()+'/'+p.getUrl());
}
}
path = ancestors.get(ig);
if(path!=null) {
return normalizeURI(path+'/'+url);
}
assert ig instanceof Item; // if not, ig must have been the Hudson instance
i = (Item) ig;
}
}
private static String normalizeURI(String uri) {
return URI.create(uri).normalize().toString();
}
/**
* Gets all the {@link TopLevelItem}s recursively in the {@link ItemGroup} tree.
*
* @since 1.512
*/
public static List<TopLevelItem> getAllTopLevelItems(ItemGroup root) {
return Items.getAllItems(root, TopLevelItem.class);
}
/**
* Gets the relative name or display name to the given item from the specified group.
*
* @since 1.515
* @param p the Item we want the relative display name
* @param g the ItemGroup used as point of reference for the item
* @param useDisplayName if true, returns a display name, otherwise returns a name
* @return
* String like "foo » bar"
*/
public static String getRelativeNameFrom(Item p, ItemGroup g, boolean useDisplayName) {
if (p == null) return null;
if (g == null) return useDisplayName ? p.getFullDisplayName() : p.getFullName();
String separationString = useDisplayName ? " » " : "/";
// first list up all the parents
Map<ItemGroup,Integer> parents = new HashMap<ItemGroup,Integer>();
int depth=0;
while (g!=null) {
parents.put(g, depth++);
if (g instanceof Item)
g = ((Item)g).getParent();
else
g = null;
}
StringBuilder buf = new StringBuilder();
Item i=p;
while (true) {
if (buf.length()>0) buf.insert(0,separationString);
buf.insert(0,useDisplayName ? i.getDisplayName() : i.getName());
ItemGroup gr = i.getParent();
Integer d = parents.get(gr);
if (d!=null) {
for (int j=d; j>0; j--) {
buf.insert(0,separationString);
buf.insert(0,"..");
}
return buf.toString();
}
if (gr instanceof Item)
i = (Item)gr;
else
return null;
}
}
/**
* Gets the name to the given item relative to given group.
*
* @since 1.515
* @param p the Item we want the relative display name
* @param g the ItemGroup used as point of reference for the item
* @return
* String like "foo/bar"
*/
public static String getRelativeNameFrom(Item p, ItemGroup g) {
return getRelativeNameFrom(p, g, false);
}
/**
* Gets the relative display name to the given item from the specified group.
*
* @since 1.512
* @param p the Item we want the relative display name
* @param g the ItemGroup used as point of reference for the item
* @return
* String like "Foo » Bar"
*/
public static String getRelativeDisplayNameFrom(Item p, ItemGroup g) {
return getRelativeNameFrom(p, g, true);
}
public static Map<Thread,StackTraceElement[]> dumpAllThreads() {
Map<Thread,StackTraceElement[]> sorted = new TreeMap<Thread,StackTraceElement[]>(new ThreadSorter());
sorted.putAll(Thread.getAllStackTraces());
return sorted;
}
public static ThreadInfo[] getThreadInfos() {
ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
return mbean.dumpAllThreads(mbean.isObjectMonitorUsageSupported(),mbean.isSynchronizerUsageSupported());
}
public static ThreadGroupMap sortThreadsAndGetGroupMap(ThreadInfo[] list) {
ThreadGroupMap sorter = new ThreadGroupMap();
Arrays.sort(list, sorter);
return sorter;
}
// Common code for sorting Threads/ThreadInfos by ThreadGroup
private static class ThreadSorterBase {
protected Map<Long,String> map = new HashMap<Long,String>();
private ThreadSorterBase() {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
while (tg.getParent() != null) tg = tg.getParent();
Thread[] threads = new Thread[tg.activeCount()*2];
int threadsLen = tg.enumerate(threads, true);
for (int i = 0; i < threadsLen; i++) {
ThreadGroup group = threads[i].getThreadGroup();
map.put(threads[i].getId(), group != null ? group.getName() : null);
}
}
protected int compare(long idA, long idB) {
String tga = map.get(idA), tgb = map.get(idB);
int result = (tga!=null?-1:0) + (tgb!=null?1:0); // Will be non-zero if only one is null
if (result==0 && tga!=null)
result = tga.compareToIgnoreCase(tgb);
return result;
}
}
public static class ThreadGroupMap extends ThreadSorterBase implements Comparator<ThreadInfo> {
/**
* @return ThreadGroup name or null if unknown
*/
public String getThreadGroup(ThreadInfo ti) {
return map.get(ti.getThreadId());
}
public int compare(ThreadInfo a, ThreadInfo b) {
int result = compare(a.getThreadId(), b.getThreadId());
if (result == 0)
result = a.getThreadName().compareToIgnoreCase(b.getThreadName());
return result;
}
}
private static class ThreadSorter extends ThreadSorterBase implements Comparator<Thread> {
public int compare(Thread a, Thread b) {
int result = compare(a.getId(), b.getId());
if (result == 0)
result = a.getName().compareToIgnoreCase(b.getName());
return result;
}
}
/**
* @deprecated Now always true.
*/
@Deprecated
public static boolean isMustangOrAbove() {
return true;
}
// ThreadInfo.toString() truncates the stack trace by first 8, so needed my own version
public static String dumpThreadInfo(ThreadInfo ti, ThreadGroupMap map) {
String grp = map.getThreadGroup(ti);
StringBuilder sb = new StringBuilder("\"" + ti.getThreadName() + "\"" +
" Id=" + ti.getThreadId() + " Group=" +
(grp != null ? grp : "?") + " " +
ti.getThreadState());
if (ti.getLockName() != null) {
sb.append(" on " + ti.getLockName());
}
if (ti.getLockOwnerName() != null) {
sb.append(" owned by \"" + ti.getLockOwnerName() +
"\" Id=" + ti.getLockOwnerId());
}
if (ti.isSuspended()) {
sb.append(" (suspended)");
}
if (ti.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
StackTraceElement[] stackTrace = ti.getStackTrace();
for (int i=0; i < stackTrace.length; i++) {
StackTraceElement ste = stackTrace[i];
sb.append("\tat ").append(ste);
sb.append('\n');
if (i == 0 && ti.getLockInfo() != null) {
Thread.State ts = ti.getThreadState();
switch (ts) {
case BLOCKED:
sb.append("\t- blocked on ").append(ti.getLockInfo());
sb.append('\n');
break;
case WAITING:
sb.append("\t- waiting on ").append(ti.getLockInfo());
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("\t- waiting on ").append(ti.getLockInfo());
sb.append('\n');
break;
default:
}
}
for (MonitorInfo mi : ti.getLockedMonitors()) {
if (mi.getLockedStackDepth() == i) {
sb.append("\t- locked ").append(mi);
sb.append('\n');
}
}
}
LockInfo[] locks = ti.getLockedSynchronizers();
if (locks.length > 0) {
sb.append("\n\tNumber of locked synchronizers = " + locks.length);
sb.append('\n');
for (LockInfo li : locks) {
sb.append("\t- ").append(li);
sb.append('\n');
}
}
sb.append('\n');
return sb.toString();
}
public static <T> Collection<T> emptyList() {
return Collections.emptyList();
}
public static String jsStringEscape(String s) {
StringBuilder buf = new StringBuilder();
for( int i=0; i<s.length(); i++ ) {
char ch = s.charAt(i);
switch(ch) {
case '\'':
buf.append("\\'");
break;
case '\\':
buf.append("\\\\");
break;
case '"':
buf.append("\\\"");
break;
default:
buf.append(ch);
}
}
return buf.toString();
}
/**
* Converts "abc" to "Abc".
*/
public static String capitalize(String s) {
if(s==null || s.length()==0) return s;
return Character.toUpperCase(s.charAt(0))+s.substring(1);
}
public static String getVersion() {
return Jenkins.VERSION;
}
/**
* Resource path prefix.
*/
public static String getResourcePath() {
return Jenkins.RESOURCE_PATH;
}
public static String getViewResource(Object it, String path) {
Class clazz = it.getClass();
if(it instanceof Class)
clazz = (Class)it;
if(it instanceof Descriptor)
clazz = ((Descriptor)it).clazz;
StringBuilder buf = new StringBuilder(Stapler.getCurrentRequest().getContextPath());
buf.append(Jenkins.VIEW_RESOURCE_PATH).append('/');
buf.append(clazz.getName().replace('.','/').replace('$','/'));
buf.append('/').append(path);
return buf.toString();
}
public static boolean hasView(Object it, String path) throws IOException {
if(it==null) return false;
return Stapler.getCurrentRequest().getView(it,path)!=null;
}
/**
* Can be used to check a checkbox by default.
* Used from views like {@code h.defaultToTrue(scm.useUpdate)}.
* The expression will evaluate to true if scm is null.
*/
public static boolean defaultToTrue(Boolean b) {
if(b==null) return true;
return b;
}
/**
* If the value exists, return that value. Otherwise return the default value.
* <p>
* Starting 1.294, JEXL supports the elvis operator "x?:y" that supersedes this.
*
* @since 1.150
*/
public static <T> T defaulted(T value, T defaultValue) {
return value!=null ? value : defaultValue;
}
/**
* Prints a stack trace from an exception into a readable form.
* Unlike {@link Throwable#printStackTrace(PrintWriter)}, this implementation follows the suggestion of JDK-6507809
* to produce a linear trace even when {@link Throwable#getCause} is used.
* @param t Input {@link Throwable}
* @return If {@code t} is not null, generally a multiline string ending in a (platform-specific) newline;
* otherwise, the method returns a default
* &quot;No exception details&quot; string.
*/
public static @Nonnull String printThrowable(@CheckForNull Throwable t) {
if (t == null) {
return Messages.Functions_NoExceptionDetails();
}
StringBuilder s = new StringBuilder();
doPrintStackTrace(s, t, null, "", new HashSet<Throwable>());
return s.toString();
}
private static void doPrintStackTrace(@Nonnull StringBuilder s, @Nonnull Throwable t, @CheckForNull Throwable higher, @Nonnull String prefix, @Nonnull Set<Throwable> encountered) {
if (!encountered.add(t)) {
s.append("<cycle to ").append(t).append(">\n");
return;
}
if (Util.isOverridden(Throwable.class, t.getClass(), "printStackTrace", PrintWriter.class)) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
s.append(sw.toString());
return;
}
Throwable lower = t.getCause();
if (lower != null) {
doPrintStackTrace(s, lower, t, prefix, encountered);
}
for (Throwable suppressed : t.getSuppressed()) {
s.append(prefix).append("Also: ");
doPrintStackTrace(s, suppressed, t, prefix + "\t", encountered);
}
if (lower != null) {
s.append(prefix).append("Caused: ");
}
String summary = t.toString();
if (lower != null) {
String suffix = ": " + lower;
if (summary.endsWith(suffix)) {
summary = summary.substring(0, summary.length() - suffix.length());
}
}
s.append(summary).append(IOUtils.LINE_SEPARATOR);
StackTraceElement[] trace = t.getStackTrace();
int end = trace.length;
if (higher != null) {
StackTraceElement[] higherTrace = higher.getStackTrace();
while (end > 0) {
int higherEnd = end + higherTrace.length - trace.length;
if (higherEnd <= 0 || !higherTrace[higherEnd - 1].equals(trace[end - 1])) {
break;
}
end--;
}
}
for (int i = 0; i < end; i++) {
s.append(prefix).append("\tat ").append(trace[i]).append(IOUtils.LINE_SEPARATOR);
}
}
/**
* Like {@link Throwable#printStackTrace(PrintWriter)} but using {@link #printThrowable} format.
* @param t an exception to print
* @param pw the log
* @since 2.43
*/
public static void printStackTrace(@CheckForNull Throwable t, @Nonnull PrintWriter pw) {
pw.println(printThrowable(t).trim());
}
/**
* Like {@link Throwable#printStackTrace(PrintStream)} but using {@link #printThrowable} format.
* @param t an exception to print
* @param ps the log
* @since 2.43
*/
public static void printStackTrace(@CheckForNull Throwable t, @Nonnull PrintStream ps) {
ps.println(printThrowable(t).trim());
}
/**
* Counts the number of rows needed for textarea to fit the content.
* Minimum 5 rows.
*/
public static int determineRows(String s) {
if(s==null) return 5;
return Math.max(5,LINE_END.split(s).length);
}
/**
* Converts the Hudson build status to CruiseControl build status,
* which is either Success, Failure, Exception, or Unknown.
*/
public static String toCCStatus(Item i) {
if (i instanceof Job) {
Job j = (Job) i;
switch (j.getIconColor()) {
case ABORTED:
case ABORTED_ANIME:
case RED:
case RED_ANIME:
case YELLOW:
case YELLOW_ANIME:
return "Failure";
case BLUE:
case BLUE_ANIME:
return "Success";
case DISABLED:
case DISABLED_ANIME:
case GREY:
case GREY_ANIME:
case NOTBUILT:
case NOTBUILT_ANIME:
return "Unknown";
}
}
return "Unknown";
}
private static final Pattern LINE_END = Pattern.compile("\r?\n");
/**
* Checks if the current user is anonymous.
*/
public static boolean isAnonymous() {
return Jenkins.getAuthentication() instanceof AnonymousAuthenticationToken;
}
/**
* When called from within JEXL expression evaluation,
* this method returns the current {@link JellyContext} used
* to evaluate the script.
*
* @since 1.164
*/
public static JellyContext getCurrentJellyContext() {
JellyContext context = ExpressionFactory2.CURRENT_CONTEXT.get();
assert context!=null;
return context;
}
/**
* Evaluate a Jelly script and return output as a String.
*
* @since 1.267
*/
public static String runScript(Script script) throws JellyTagException {
StringWriter out = new StringWriter();
script.run(getCurrentJellyContext(), XMLOutput.createXMLOutput(out));
return out.toString();
}
/**
* Returns a sub-list if the given list is bigger than the specified {@code maxSize}.
* <strong>Warning:</strong> do not call this with a {@link RunList}, or you will break lazy loading!
*/
public static <T> List<T> subList(List<T> base, int maxSize) {
if(maxSize<base.size())
return base.subList(0,maxSize);
else
return base;
}
/**
* Combine path components via '/' while handling leading/trailing '/' to avoid duplicates.
*/
public static String joinPath(String... components) {
StringBuilder buf = new StringBuilder();
for (String s : components) {
if (s.length()==0) continue;
if (buf.length()>0) {
if (buf.charAt(buf.length()-1)!='/')
buf.append('/');
if (s.charAt(0)=='/') s=s.substring(1);
}
buf.append(s);
}
return buf.toString();
}
/**
* Computes the hyperlink to actions, to handle the situation when the {@link Action#getUrlName()}
* returns absolute URL.
*
* @return null in case the action should not be presented to the user.
*/
public static @CheckForNull String getActionUrl(String itUrl,Action action) {
String urlName = action.getUrlName();
if(urlName==null) return null; // Should not be displayed
try {
if (new URI(urlName).isAbsolute()) {
return urlName;
}
} catch (URISyntaxException x) {
Logger.getLogger(Functions.class.getName()).log(Level.WARNING, "Failed to parse URL for {0}: {1}", new Object[] {action, x});
return null;
}
if(urlName.startsWith("/"))
return joinPath(Stapler.getCurrentRequest().getContextPath(),urlName);
else
// relative URL name
return joinPath(Stapler.getCurrentRequest().getContextPath()+'/'+itUrl,urlName);
}
/**
* Escapes the character unsafe for e-mail address.
* See http://en.wikipedia.org/wiki/E-mail_address for the details,
* but here the vocabulary is even more restricted.
*/
public static String toEmailSafeString(String projectName) {
// TODO: escape non-ASCII characters
StringBuilder buf = new StringBuilder(projectName.length());
for( int i=0; i<projectName.length(); i++ ) {
char ch = projectName.charAt(i);
if(('a'<=ch && ch<='z')
|| ('z'<=ch && ch<='Z')
|| ('0'<=ch && ch<='9')
|| "-_.".indexOf(ch)>=0)
buf.append(ch);
else
buf.append('_'); // escape
}
return projectName;
}
/**
* Obtains the host name of the Hudson server that clients can use to talk back to.
* <p>
* This is primarily used in <tt>slave-agent.jnlp.jelly</tt> to specify the destination
* that the agents talk to.
*/
public String getServerName() {
// Try to infer this from the configured root URL.
// This makes it work correctly when Hudson runs behind a reverse proxy.
String url = Jenkins.getInstance().getRootUrl();
try {
if(url!=null) {
String host = new URL(url).getHost();
if(host!=null)
return host;
}
} catch (MalformedURLException e) {
// fall back to HTTP request
}
return Stapler.getCurrentRequest().getServerName();
}
/**
* Determines the form validation check URL. See textbox.jelly
*
* @deprecated
* Use {@link #calcCheckUrl}
*/
@Deprecated
public String getCheckUrl(String userDefined, Object descriptor, String field) {
if(userDefined!=null || field==null) return userDefined;
if (descriptor instanceof Descriptor) {
Descriptor d = (Descriptor) descriptor;
return d.getCheckUrl(field);
}
return null;
}
/**
* Determines the parameters that client-side needs for a form validation check. See prepareDatabinding.jelly
* @since 1.528
*/
public void calcCheckUrl(Map attributes, String userDefined, Object descriptor, String field) {
if(userDefined!=null || field==null) return;
if (descriptor instanceof Descriptor) {
Descriptor d = (Descriptor) descriptor;
CheckMethod m = d.getCheckMethod(field);
attributes.put("checkUrl",m.toStemUrl());
attributes.put("checkDependsOn",m.getDependsOn());
}
}
/**
* If the given href link is matching the current page, return true.
*
* Used in <tt>task.jelly</tt> to decide if the page should be highlighted.
*/
public boolean hyperlinkMatchesCurrentPage(String href) throws UnsupportedEncodingException {
String url = Stapler.getCurrentRequest().getRequestURL().toString();
if (href == null || href.length() <= 1) return ".".equals(href) && url.endsWith("/");
url = URLDecoder.decode(url,"UTF-8");
href = URLDecoder.decode(href,"UTF-8");
if (url.endsWith("/")) url = url.substring(0, url.length() - 1);
if (href.endsWith("/")) href = href.substring(0, href.length() - 1);
return url.endsWith(href);
}
public <T> List<T> singletonList(T t) {
return Collections.singletonList(t);
}
/**
* Gets all the {@link PageDecorator}s.
*/
public static List<PageDecorator> getPageDecorators() {
// this method may be called to render start up errors, at which point Hudson doesn't exist yet. see HUDSON-3608
if(Jenkins.getInstanceOrNull()==null) return Collections.emptyList();
return PageDecorator.all();
}
public static List<Descriptor<Cloud>> getCloudDescriptors() {
return Cloud.all();
}
/**
* Prepend a prefix only when there's the specified body.
*/
public String prepend(String prefix, String body) {
if(body!=null && body.length()>0)
return prefix+body;
return body;
}
public static List<Descriptor<CrumbIssuer>> getCrumbIssuerDescriptors() {
return CrumbIssuer.all();
}
public static String getCrumb(StaplerRequest req) {
Jenkins h = Jenkins.getInstanceOrNull();
CrumbIssuer issuer = h != null ? h.getCrumbIssuer() : null;
return issuer != null ? issuer.getCrumb(req) : "";
}
public static String getCrumbRequestField() {
Jenkins h = Jenkins.getInstanceOrNull();
CrumbIssuer issuer = h != null ? h.getCrumbIssuer() : null;
return issuer != null ? issuer.getDescriptor().getCrumbRequestField() : "";
}
public static Date getCurrentTime() {
return new Date();
}
public static Locale getCurrentLocale() {
Locale locale=null;
StaplerRequest req = Stapler.getCurrentRequest();
if(req!=null)
locale = req.getLocale();
if(locale==null)
locale = Locale.getDefault();
return locale;
}
/**
* Generate a series of &lt;script> tags to include <tt>script.js</tt>
* from {@link ConsoleAnnotatorFactory}s and {@link ConsoleAnnotationDescriptor}s.
*/
public static String generateConsoleAnnotationScriptAndStylesheet() {
String cp = Stapler.getCurrentRequest().getContextPath();
StringBuilder buf = new StringBuilder();
for (ConsoleAnnotatorFactory f : ConsoleAnnotatorFactory.all()) {
String path = cp + "/extensionList/" + ConsoleAnnotatorFactory.class.getName() + "/" + f.getClass().getName();
if (f.hasScript())
buf.append("<script src='").append(path).append("/script.js'></script>");
if (f.hasStylesheet())
buf.append("<link rel='stylesheet' type='text/css' href='").append(path).append("/style.css' />");
}
for (ConsoleAnnotationDescriptor d : ConsoleAnnotationDescriptor.all()) {
String path = cp+"/descriptor/"+d.clazz.getName();
if (d.hasScript())
buf.append("<script src='").append(path).append("/script.js'></script>");
if (d.hasStylesheet())
buf.append("<link rel='stylesheet' type='text/css' href='").append(path).append("/style.css' />");
}
return buf.toString();
}
/**
* Work around for bug 6935026.
*/
public List<String> getLoggerNames() {
while (true) {
try {
List<String> r = new ArrayList<String>();
Enumeration<String> e = LogManager.getLogManager().getLoggerNames();
while (e.hasMoreElements())
r.add(e.nextElement());
return r;
} catch (ConcurrentModificationException e) {
// retry
}
}
}
/**
* Used by &lt;f:password/> so that we send an encrypted value to the client.
*/
public String getPasswordValue(Object o) {
if (o==null) return null;
if (o instanceof Secret) {
StaplerRequest req = Stapler.getCurrentRequest();
if (req != null) {
Item item = req.findAncestorObject(Item.class);
if (item != null && !item.hasPermission(Item.CONFIGURE)) {
return "********";
}
}
return ((Secret) o).getEncryptedValue();
}
if (getIsUnitTest() && !o.equals(PasswordParameterDefinition.DEFAULT_VALUE)) {
throw new SecurityException("attempted to render plaintext ‘" + o + "’ in password field; use a getter of type Secret instead");
}
return o.toString();
}
public List filterDescriptors(Object context, Iterable descriptors) {
return DescriptorVisibilityFilter.apply(context,descriptors);
}
/**
* Returns true if we are running unit tests.
*/
public static boolean getIsUnitTest() {
return Main.isUnitTest;
}
/**
* Returns {@code true} if the {@link Run#ARTIFACTS} permission is enabled,
* {@code false} otherwise.
*
* <p>When the {@link Run#ARTIFACTS} permission is not turned on using the
* {@code hudson.security.ArtifactsPermission} system property, this
* permission must not be considered to be set to {@code false} for every
* user. It must rather be like if the permission doesn't exist at all
* (which means that every user has to have an access to the artifacts but
* the permission can't be configured in the security screen). Got it?</p>
*/
public static boolean isArtifactsPermissionEnabled() {
return SystemProperties.getBoolean("hudson.security.ArtifactsPermission");
}
/**
* Returns {@code true} if the {@link Item#WIPEOUT} permission is enabled,
* {@code false} otherwise.
*
* <p>The "Wipe Out Workspace" action available on jobs is controlled by the
* {@link Item#BUILD} permission. For some specific projects, however, it is
* not acceptable to let users have this possibility, even it they can
* trigger builds. As such, when enabling the {@code hudson.security.WipeOutPermission}
* system property, a new "WipeOut" permission will allow to have greater
* control on the "Wipe Out Workspace" action.</p>
*/
public static boolean isWipeOutPermissionEnabled() {
return SystemProperties.getBoolean("hudson.security.WipeOutPermission");
}
public static String createRenderOnDemandProxy(JellyContext context, String attributesToCapture) {
return Stapler.getCurrentRequest().createJavaScriptProxy(new RenderOnDemandClosure(context,attributesToCapture));
}
public static String getCurrentDescriptorByNameUrl() {
return Descriptor.getCurrentDescriptorByNameUrl();
}
public static String setCurrentDescriptorByNameUrl(String value) {
String o = getCurrentDescriptorByNameUrl();
Stapler.getCurrentRequest().setAttribute("currentDescriptorByNameUrl", value);
return o;
}
public static void restoreCurrentDescriptorByNameUrl(String old) {
Stapler.getCurrentRequest().setAttribute("currentDescriptorByNameUrl", old);
}
public static List<String> getRequestHeaders(String name) {
List<String> r = new ArrayList<String>();
Enumeration e = Stapler.getCurrentRequest().getHeaders(name);
while (e.hasMoreElements()) {
r.add(e.nextElement().toString());
}
return r;
}
/**
* Used for arguments to internationalized expressions to avoid escape
*/
public static Object rawHtml(Object o) {
return o==null ? null : new RawHtmlArgument(o);
}
public static ArrayList<CLICommand> getCLICommands() {
ArrayList<CLICommand> all = new ArrayList<CLICommand>(CLICommand.all());
Collections.sort(all, new Comparator<CLICommand>() {
public int compare(CLICommand cliCommand, CLICommand cliCommand1) {
return cliCommand.getName().compareTo(cliCommand1.getName());
}
});
return all;
}
/**
* Returns an avatar image URL for the specified user and preferred image size
* @param user the user
* @param avatarSize the preferred size of the avatar image
* @return a URL string
* @since 1.433
*/
public static String getAvatar(User user, String avatarSize) {
return UserAvatarResolver.resolve(user, avatarSize);
}
/**
* @deprecated as of 1.451
* Use {@link #getAvatar}
*/
@Deprecated
public String getUserAvatar(User user, String avatarSize) {
return getAvatar(user,avatarSize);
}
/**
* Returns human readable information about file size
*
* @param size file size in bytes
* @return file size in appropriate unit
*/
public static String humanReadableByteSize(long size){
String measure = "B";
if(size < 1024){
return size + " " + measure;
}
Double number = new Double(size);
if(number>=1024){
number = number/1024;
measure = "KB";
if(number>=1024){
number = number/1024;
measure = "MB";
if(number>=1024){
number=number/1024;
measure = "GB";
}
}
}
DecimalFormat format = new DecimalFormat("#0.00");
return format.format(number) + " " + measure;
}
/**
* Get a string that can be safely broken to several lines when necessary.
*
* This implementation inserts &lt;wbr> tags into string. It allows browsers
* to wrap line before any sequence of punctuation characters or anywhere
* in the middle of prolonged sequences of word characters.
*
* @since 1.517
*/
public static String breakableString(final String plain) {
if (plain == null) {
return null;
}
return plain.replaceAll("([\\p{Punct}&&[^;]]+\\w)", "<wbr>$1")
.replaceAll("([^\\p{Punct}\\s-]{20})(?=[^\\p{Punct}\\s-]{10})", "$1<wbr>")
;
}
/**
* Advertises the minimum set of HTTP headers that assist programmatic
* discovery of Jenkins.
*/
public static void advertiseHeaders(HttpServletResponse rsp) {
Jenkins j = Jenkins.getInstanceOrNull();
if (j!=null) {
rsp.setHeader("X-Hudson","1.395");
rsp.setHeader("X-Jenkins", Jenkins.VERSION);
rsp.setHeader("X-Jenkins-Session", Jenkins.SESSION_HASH);
TcpSlaveAgentListener tal = j.tcpSlaveAgentListener;
if (tal !=null) {
int p = tal.getAdvertisedPort();
rsp.setIntHeader("X-Hudson-CLI-Port", p);
rsp.setIntHeader("X-Jenkins-CLI-Port", p);
rsp.setIntHeader("X-Jenkins-CLI2-Port", p);
rsp.setHeader("X-Jenkins-CLI-Host", TcpSlaveAgentListener.CLI_HOST_NAME);
}
}
}
@Restricted(NoExternalUse.class) // for actions.jelly and ContextMenu.add
public static boolean isContextMenuVisible(Action a) {
if (a instanceof ModelObjectWithContextMenu.ContextMenuVisibility) {
return ((ModelObjectWithContextMenu.ContextMenuVisibility) a).isVisible();
} else {
return true;
}
}
}