All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kohsuke.stapler.Stapler Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2004-2010, Kohsuke Kawaguchi
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of
 *       conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.kohsuke.stapler;

import net.sf.json.JSONObject;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.converters.DoubleConverter;
import org.apache.commons.beanutils.converters.FloatConverter;
import org.apache.commons.beanutils.converters.IntegerConverter;
import org.apache.commons.fileupload.FileItem;
import org.kohsuke.stapler.bind.BoundObjectTable;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static javax.servlet.http.HttpServletResponse.*;
import static org.kohsuke.stapler.Dispatcher.*;


/**
 * Maps an HTTP request to a method call / JSP invocation against a model object
 * by evaluating the request URL in a EL-ish way.
 *
 * 

* This servlet should be used as the default servlet. * * @author Kohsuke Kawaguchi */ public class Stapler extends HttpServlet { private /*final*/ ServletContext context; private /*final*/ WebApp webApp; public @Override void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); this.context = servletConfig.getServletContext(); this.webApp = WebApp.get(context); String defaultEncodings = servletConfig.getInitParameter("default-encodings"); if(defaultEncodings!=null) { for(String t : defaultEncodings.split(";")) { t=t.trim(); int idx=t.indexOf('='); if(idx<0) throw new ServletException("Invalid format: "+t); webApp.defaultEncodingForStaticResources.put(t.substring(0,idx),t.substring(idx+1)); } } } public WebApp getWebApp() { return webApp; } protected @Override void service(HttpServletRequest req, HttpServletResponse rsp) throws ServletException, IOException { Thread t = Thread.currentThread(); final String oldName = t.getName(); try { t.setName("Handling "+req.getMethod()+' '+req.getRequestURI()+" : "+oldName); String servletPath = getServletPath(req); if(LOGGER.isLoggable(Level.FINE)) LOGGER.fine("Processing request for "+servletPath); if (servletPath.startsWith(BoundObjectTable.PREFIX)) { // serving exported objects invoke( req, rsp, webApp.boundObjectTable, servletPath.substring(BoundObjectTable.PREFIX.length())); return; } boolean staticLink = false; if(servletPath.startsWith("/static/")) { // skip "/static/..../ portion int idx = servletPath.indexOf('/',8); servletPath=servletPath.substring(idx); staticLink = true; } String resPath = servletPath.toLowerCase(Locale.ENGLISH); if (servletPath.length() != 0 && !resPath.startsWith("/web-inf") && !resPath.startsWith("/meta-inf")) { // getResource requires '/' prefix (and resin insists on that, too) but servletPath can be empty string (hudson #879) // so make sure servletPath is at least length 1 before calling getResource() // WEB-INF is by convention hidden and not supposed to be rendered to clients (HUDSON-7457) OpenConnection con = openResourcePathByLocale(req,servletPath); if(con!=null) { long expires = MetaClass.NO_CACHE ? 0 : 24L * 60 * 60 * 1000; /*1 day*/ if(staticLink) expires*=365; // static resources are unique, so we can set a long expiration date if(serveStaticResource(req, new ResponseImpl(this, rsp), con, expires)) return; // done } } Object root = webApp.getApp(); if(root==null) throw new ServletException("there's no \"app\" attribute in the application context."); // consider reusing this ArrayList. invoke( req, rsp, root, servletPath); } finally { t.setName(oldName); } } /** * Tomcat and GlassFish returns a fresh {@link InputStream} every time * {@link URLConnection#getInputStream()} is invoked in their {@code org.apache.naming.resources.DirContextURLConnection}. * *

* All the other {@link URLConnection}s in JDK don't do this --- they return the same {@link InputStream}, * even the one for the file:// URLs. * *

* In Tomcat (and most likely in GlassFish, although this is not verified), resource look up on * {@link ServletContext#getResource(String)} successfully returns non-existent URL, and * the failure can be only detected by {@link IOException} from {@link URLConnection#getInputStream()}. * *

* Therefore, for the whole thing to work without resource leak, once we open {@link InputStream} * for the sake of really making sure that the resource exists, we need to hang on to that stream. * *

* Hence the need for this tuple. */ private static final class OpenConnection { final URLConnection connection; final InputStream stream; private OpenConnection(URLConnection connection, InputStream stream) { this.connection = connection; this.stream = stream; } private OpenConnection(URLConnection connection) throws IOException { this(connection, connection.getInputStream()); } private void close() throws IOException { stream.close(); } } private OpenConnection openResourcePathByLocale(HttpServletRequest req,String resourcePath) throws IOException { URL url = getServletContext().getResource(resourcePath); if(url==null) return null; return selectResourceByLocale(url,req.getLocale()); } /** * Basically works like {@link URL#openConnection()} but it uses the * locale specific resource if available, by using the given locale. * *

* The syntax of the locale specific resource is the same as property file localization. * So Japanese resource for foo.html would be named foo_ja.html. */ OpenConnection selectResourceByLocale(URL url, Locale locale) throws IOException { String s = url.toString(); int idx = s.lastIndexOf('.'); if(idx<0) // no file extension, so no locale switch available return openURL(url); String base = s.substring(0,idx); String ext = s.substring(idx); if(ext.indexOf('/')>=0) // the '.' we found was not an extension separator return openURL(url); OpenConnection con; // try locale specific resources first. con = openURL(new URL(base+'_'+ locale.getLanguage()+'_'+ locale.getCountry()+'_'+ locale.getVariant()+ext)); if(con!=null) return con; con = openURL(new URL(base+'_'+ locale.getLanguage()+'_'+ locale.getCountry()+ext)); if(con!=null) return con; con = openURL(new URL(base+'_'+ locale.getLanguage()+ext)); if(con!=null) return con; // default return openURL(url); } /** * Serves the specified {@link URLConnection} as a static resource. */ boolean serveStaticResource(HttpServletRequest req, StaplerResponse rsp, OpenConnection con, long expiration) throws IOException { if (con == null) { return false; } try { return serveStaticResource(req, rsp, con.stream, con.connection.getLastModified(), expiration, con.connection.getContentLength(), con.connection.getURL().toString()); } finally { con.close(); } } /** * Serves the specified {@link URL} as a static resource. */ boolean serveStaticResource(HttpServletRequest req, StaplerResponse rsp, URL url, long expiration) throws IOException { return serveStaticResource(req,rsp,openURL(url),expiration); } /** * Opens URL, with error handling to absorb container differences. *

* This method returns null if the resource pointed by URL doesn't exist. The initial attempt was to * distinguish "resource exists but failed to load" vs "resource doesn't exist", but as more reports * from the field come in, we discovered that it's impossible to make such a distinction and work with * many environments both at the same time. */ private OpenConnection openURL(URL url) { if(url==null) return null; // jetty reports directories as URLs, which isn't what this is intended for, // so check and reject. File f = toFile(url); if(f!=null && f.isDirectory()) return null; try { // in normal protocol handlers like http/file, openConnection doesn't actually open a connection // (that's deferred until URLConnection.connect()), so this method doesn't result in an error, // even if URL points to something that doesn't exist. // // but we've heard a report from http://github.com/adreghiciu that some URLS backed by custom // protocol handlers can throw an exception as early as here. So treat this IOException // as "the resource pointed by URL is missing". URLConnection con = url.openConnection(); OpenConnection c = new OpenConnection(con); // Some URLs backed by custom broken protocol handler can return null from getInputStream(), // so let's be defensive here. An example of that is an OSGi container --- unfortunately // we don't have more details than that. if(c.stream==null) return null; return c; } catch (IOException e) { // Tomcat only reports a missing resource error here, from URLConnection.getInputStream() return null; } } /** * Serves the specified {@link InputStream} as a static resource. * * @param contentLength * if the length of the input stream is known in advance, specify that value * so that HTTP keep-alive works. Otherwise specify -1 to indicate that the length is unknown. * @param expiration * The number of milliseconds until the resource will "expire". * Until it expires the browser will be allowed to cache it * and serve it without checking back with the server. * After it expires, the client will send conditional GET to * check if the resource is actually modified or not. * If 0, it will immediately expire. * @param fileName * file name of this resource. Used to determine the MIME type. * Since the only important portion is the file extension, this could be just a file name, * or a full path name, or even a pseudo file name that doesn't actually exist. * It supports both '/' and '\\' as the path separator. * @return false * if the resource doesn't exist. */ boolean serveStaticResource(HttpServletRequest req, StaplerResponse rsp, InputStream in, long lastModified, long expiration, long contentLength, String fileName) throws IOException { try { {// send out Last-Modified, or check If-Modified-Since if(lastModified!=0) { String since = req.getHeader("If-Modified-Since"); SimpleDateFormat format = HTTP_DATE_FORMAT.get(); if(since!=null) { try { long ims = format.parse(since).getTime(); if(lastModified 0 && (thisSkip = dis.skipBytes((int)Math.min(toSkip, Integer.MAX_VALUE))) > 0) toSkip -= thisSkip; if (toSkip > 0) throw new IOException( "skipBytes failure (" + toSkip + " of " + s + " bytes unskipped)"); in = new TruncatedInputStream(in,e-s); contentLength = Math.min(e-s,contentLength); } // if the Range header doesn't look like what we can handle, // pretend as if we didn't understand it, instead of doing a proper error reporting } } if (out == null) { if(contentLength!=-1) rsp.setHeader("Content-Length", Long.toString(contentLength)); out = rsp.getOutputStream(); } byte[] buf = new byte[1024]; int len; while((len=in.read(buf))>0) out.write(buf,0,len); out.close(); return true; } finally { in.close(); } } /** * Strings like "5-300", "0-900", or "100-" */ private static final Pattern RANGE_SPEC = Pattern.compile("([\\d]+)-([\\d]*)"); private String getMimeType(String fileName) { if(fileName.startsWith("mime-type:")) return fileName.substring("mime-type:".length()); int idx = fileName.lastIndexOf('/'); fileName = fileName.substring(idx+1); idx = fileName.lastIndexOf('\\'); fileName = fileName.substring(idx+1); String extension = fileName.substring(fileName.lastIndexOf('.')+1); String mimeType = webApp.mimeTypes.get(extension); if(mimeType==null) mimeType = getServletContext().getMimeType(fileName); if(mimeType==null) mimeType="application/octet-stream"; if(webApp.defaultEncodingForStaticResources.containsKey(mimeType)) mimeType += ";charset="+webApp.defaultEncodingForStaticResources.get(mimeType); return mimeType; } /** * If the URL is "file://", return its file representation. */ private File toFile(URL url) { String urlstr = url.toExternalForm(); if(!urlstr.startsWith("file:")) return null; try { //when URL contains escapes like %20, this does the conversion correctly return new File(url.toURI()); } catch (URISyntaxException e) { try { // some containers, such as Winstone, doesn't escape ' ', and for those // we need to do this. This method doesn't fail when urlstr contains '%20', // so toURI() has to be tried first. return new File(new URI(null,urlstr,null).getPath()); } catch (URISyntaxException _) { // the whole thing could fail anyway. return null; } } } /** * Performs stapler processing on the given root object and request URL. */ public void invoke(HttpServletRequest req, HttpServletResponse rsp, Object root, String url) throws IOException, ServletException { RequestImpl sreq = new RequestImpl(this, req, new ArrayList(), new TokenList(url)); RequestImpl oreq = CURRENT_REQUEST.get(); CURRENT_REQUEST.set(sreq); ResponseImpl srsp = new ResponseImpl(this, rsp); ResponseImpl orsp = CURRENT_RESPONSE.get(); CURRENT_RESPONSE.set(srsp); try { invoke(sreq,srsp,root); } finally { CURRENT_REQUEST.set(oreq); CURRENT_RESPONSE.set(orsp); } } /** * Try to dispatch the request against the given node, and if it fails, return false. * * @see #invoke(RequestImpl, ResponseImpl, Object) */ boolean tryInvoke(RequestImpl req, ResponseImpl rsp, Object node ) throws IOException, ServletException { if(traceable()) traceEval(req,rsp,node); if(node instanceof StaplerProxy) { if(traceable()) traceEval(req,rsp,node,"((StaplerProxy)",").getTarget()"); Object n = ((StaplerProxy)node).getTarget(); if(n==node || n==null) { // if the proxy returns itself, assume that it doesn't want to proxy. // if null, no one will handle the request } else { // recursion helps debugging by leaving the trace in the stack. invoke(req,rsp,n); return true; } } // adds this node to ancestor list AncestorImpl a = new AncestorImpl(req.ancestors); a.set(node,req); // try overrides if (node instanceof StaplerOverridable) { StaplerOverridable o = (StaplerOverridable) node; Collection list = o.getOverrides(); if (list!=null) { int count = 0; for (Object subject : list) { if (subject==null) continue; if(traceable()) traceEval(req,rsp,node,"((StaplerOverridable)",").getOverrides()["+(count++)+']'); if (tryInvoke(req,rsp,subject)) return true; } } } MetaClass metaClass = webApp.getMetaClass(node.getClass()); if(!req.tokens.hasMore()) { String servletPath = getServletPath(req); if(!servletPath.endsWith("/")) { String target = req.getContextPath() + servletPath + '/'; if(req.getQueryString()!=null) target += '?' + req.getQueryString(); if(LOGGER.isLoggable(Level.FINER)) LOGGER.finer("Redirecting to "+target); rsp.sendRedirect2(target); return true; } if(req.getMethod().equals("DELETE")) { if(node instanceof HttpDeletable) { ((HttpDeletable)node).delete(req,rsp); return true; } } for (Facet f : webApp.facets) { if(f.handleIndexRequest(req,rsp,node,metaClass)) return true; } URL indexHtml = getSideFileURL(node,"index.html"); if(indexHtml!=null && serveStaticResource(req,rsp,indexHtml,0)) return true; // done } try { for( Dispatcher d : metaClass.dispatchers ) { if(d.dispatch(req,rsp,node)) { if(LOGGER.isLoggable(Level.FINER)) LOGGER.finer("Handled by "+d); return true; } } } catch (IllegalAccessException e) { // this should never really happen getServletContext().log("Error while serving "+req.getRequestURL(),e); throw new ServletException(e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause == null) { // ??? getServletContext().log("Error while serving " + req.getRequestURL(), e); throw new ServletException(); } // allow the exception from the dispatch to be handled. This is handy to throw HttpResponse as an exception // from the getXyz method. for (HttpResponseRenderer r : webApp.getResponseRenderers()) if (r.generateResponse(req,rsp,node,cause)) return true; StringBuffer url = req.getRequestURL(); if (cause instanceof IOException) { getServletContext().log("Error while serving " + url, e); throw (IOException) cause; } if (cause instanceof ServletException) { getServletContext().log("Error while serving " + url, e); throw (ServletException) cause; } for (Class c = cause.getClass(); c != null; c = c.getSuperclass()) { if (c == Object.class) { getServletContext().log("Error while serving " + url, e); } else if (c.getName().equals("org.acegisecurity.AccessDeniedException")) { // [HUDSON-4834] A stack trace is too noisy for this; could just need to log in. // (Could consider doing this for all AcegiSecurityException's.) getServletContext().log("While serving " + url + ": " + cause); break; } } throw new ServletException(cause); } if(node instanceof StaplerFallback) { if(traceable()) traceEval(req,rsp,node,"((StaplerFallback)",").getStaplerFallback()"); Object n = ((StaplerFallback)node).getStaplerFallback(); if(n!=node && n!=null) { // delegate to the fallback object invoke(req,rsp,n); return true; } } return false; } /** * Try to dispatch the request against the given node, and if it fails, report an error to the client. */ void invoke(RequestImpl req, ResponseImpl rsp, Object node ) throws IOException, ServletException { if(node==null) { // node is null if(!Dispatcher.isTraceEnabled(req)) { rsp.sendError(SC_NOT_FOUND); } else { // show error page rsp.setStatus(SC_NOT_FOUND); rsp.setContentType("text/html;charset=UTF-8"); PrintWriter w = rsp.getWriter(); w.println(""); w.println("

404 Not Found

"); w.println("

Stapler processed this HTTP request as follows, but couldn't find the resource to consume the request"); w.println("

");
                EvaluationTrace.get(req).printHtml(w);
                w.println("-> unexpected null!");
                w.println("
"); w.println("

If this 404 is unexpected, double check the last part of the trace to see if it should have evaluated to null."); w.println(""); } return; } if (tryInvoke(req,rsp,node)) return; // done // we really run out of options. if(!Dispatcher.isTraceEnabled(req)) { rsp.sendError(SC_NOT_FOUND); } else { // show error page rsp.setStatus(SC_NOT_FOUND); rsp.setContentType("text/html;charset=UTF-8"); PrintWriter w = rsp.getWriter(); w.println(""); w.println("

404 Not Found

"); w.println("

Stapler processed this HTTP request as follows, but couldn't find the resource to consume the request"); w.println("

");
            EvaluationTrace.get(req).printHtml(w);
            w.printf("-> No matching rule was found on <%s> for \"%s\"\n",node,req.tokens.assembleOriginalRestOfPath());
            w.println("
"); w.printf("

<%s> has the following URL mappings, in the order of preference:",node); w.println("

    "); MetaClass metaClass = webApp.getMetaClass(node.getClass()); for (Dispatcher d : metaClass.dispatchers) { w.println("
  1. "); w.println(d.toString()); } w.println("
"); w.println(""); } } public void forward(RequestDispatcher dispatcher, StaplerRequest req, HttpServletResponse rsp) throws ServletException, IOException { dispatcher.forward(req,new ResponseImpl(this,rsp)); } private URL getSideFileURL(Object node,String fileName) throws MalformedURLException { for( Class c = node.getClass(); c!=Object.class; c=c.getSuperclass() ) { String name = "/WEB-INF/side-files/"+c.getName().replace('.','/')+'/'+fileName; URL url = getServletContext().getResource(name); if(url!=null) return url; } return null; } /** * Gets the URL (e.g., "/WEB-INF/side-files/fully/qualified/class/name/jspName") * from a class and the JSP name. */ public static String getViewURL(Class clazz,String jspName) { return "/WEB-INF/side-files/"+clazz.getName().replace('.','/')+'/'+jspName; } /** * Sets the specified object as the root of the web application. * *

* This method should be invoked from your implementation of * {@link ServletContextListener#contextInitialized(ServletContextEvent)}. * *

* This is just a convenience method to invoke * servletContext.setAttribute("app",rootApp). * *

* The root object is bound to the URL '/' and used to resolve * all the requests to this web application. */ public static void setRoot( ServletContextEvent event, Object rootApp ) { event.getServletContext().setAttribute("app",rootApp); } /** * Sets the classloader used by {@link StaplerRequest#bindJSON(Class, JSONObject)} and its sibling methods. * * @deprecated * Use {@link WebApp#setClassLoader(ClassLoader)} */ public static void setClassLoader( ServletContext context, ClassLoader classLoader ) { WebApp.get(context).setClassLoader(classLoader); } /** * @deprecated * Use {@link WebApp#getClassLoader()} */ public static ClassLoader getClassLoader( ServletContext context ) { return WebApp.get(context).getClassLoader(); } /** * @deprecated * Use {@link WebApp#getClassLoader()} */ public ClassLoader getClassLoader() { return webApp.getClassLoader(); } /** * Gets the current {@link StaplerRequest} that the calling thread is associated with. */ public static StaplerRequest getCurrentRequest() { return CURRENT_REQUEST.get(); } /** * Gets the current {@link StaplerResponse} that the calling thread is associated with. */ public static StaplerResponse getCurrentResponse() { return CURRENT_RESPONSE.get(); } /** * Gets the current {@link Stapler} that the calling thread is associated with. */ public static Stapler getCurrent() { return CURRENT_REQUEST.get().getStapler(); } /** * HTTP date format. Notice that {@link SimpleDateFormat} is thread unsafe. */ static final ThreadLocal HTTP_DATE_FORMAT = new ThreadLocal() { protected @Override SimpleDateFormat initialValue() { // RFC1945 section 3.3 Date/Time Formats states that timezones must be in GMT SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); format.setTimeZone(TimeZone.getTimeZone("GMT")); return format; } }; /*package*/ static ThreadLocal CURRENT_REQUEST = new ThreadLocal(); /*package*/ static ThreadLocal CURRENT_RESPONSE = new ThreadLocal(); private static final Logger LOGGER = Logger.getLogger(Stapler.class.getName()); /** * Extensions that look like text files. */ private static final Set TEXT_FILES = new HashSet(Arrays.asList( "css","js","html","txt","java","htm","c","cpp","h","rb","pl","py","xml" )); /** * Get raw servlet path (decoded in TokenList). */ private String getServletPath(HttpServletRequest req) { return canonicalPath(req.getRequestURI().substring(req.getContextPath().length())); } /** * Some web containers (e.g., Winstone) leaves ".." and "." in the request URL, * which is a security risk. Fix that by normalizing them. */ static String canonicalPath(String path) { List r = new ArrayList(Arrays.asList(path.split("/+"))); for (int i=0; i0) { r.remove(i-1); i--; } } else { i++; } } StringBuilder buf = new StringBuilder(); if (path.startsWith("/")) buf.append('/'); boolean first = true; for (String token : r) { if (!first) buf.append('/'); else first = false; buf.append(token); } // translation: if (path.endsWith("/") && !buf.endsWith("/")) if (path.endsWith("/") && (buf.length()==0 || buf.charAt(buf.length()-1)!='/')) buf.append('/'); return buf.toString(); } /** * This is the {@link Converter} registry that Stapler uses, primarily * for form-to-JSON binding in {@link StaplerRequest#bindJSON(Class, JSONObject)} * and its family of methods. */ public static final ConvertUtilsBean CONVERT_UTILS = new ConvertUtilsBean(); public static Converter lookupConverter(Class type) { Converter c = CONVERT_UTILS.lookup(type); if (c!=null) return c; // fall back to compatibility behavior c = ConvertUtils.lookup(type); if (c!=null) return c; // look for the associated converter try { if(type.getClassLoader()==null) return null; Class cl = type.getClassLoader().loadClass(type.getName() + "$StaplerConverterImpl"); c = (Converter)cl.newInstance(); CONVERT_UTILS.register(c,type); return c; } catch (ClassNotFoundException e) { return null; } catch (IllegalAccessException e) { IllegalAccessError x = new IllegalAccessError(); x.initCause(e); throw x; } catch (InstantiationException e) { InstantiationError x = new InstantiationError(); x.initCause(e); throw x; } } static { CONVERT_UTILS.register(new Converter() { public Object convert(Class type, Object value) { if(value==null) return null; try { return new URL(value.toString()); } catch (MalformedURLException e) { throw new ConversionException(e); } } }, URL.class); CONVERT_UTILS.register(new Converter() { public FileItem convert(Class type, Object value) { if(value==null) return null; try { return Stapler.getCurrentRequest().getFileItem(value.toString()); } catch (ServletException e) { throw new ConversionException(e); } catch (IOException e) { throw new ConversionException(e); } } }, FileItem.class); // mapping for boxed types should map null to null, instead of null to zero. CONVERT_UTILS.register(new IntegerConverter(null),Integer.class); CONVERT_UTILS.register(new FloatConverter(null),Float.class); CONVERT_UTILS.register(new DoubleConverter(null),Double.class); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy