diff --git a/core/pom.xml b/core/pom.xml index 7ce3c78662c4..054a782e4c28 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -42,7 +42,7 @@ THE SOFTWARE. true - 1.218 + 1.220 2.5.6.SEC03 1.8.9 diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java index e0cc80c5ea3b..15df186793e3 100644 --- a/core/src/main/java/hudson/model/Descriptor.java +++ b/core/src/main/java/hudson/model/Descriptor.java @@ -29,6 +29,7 @@ import hudson.XmlFile; import hudson.BulkChange; import hudson.Util; +import hudson.init.Initializer; import hudson.model.listeners.SaveableListener; import hudson.util.FormApply; import hudson.util.FormValidation.CheckMethod; @@ -62,6 +63,7 @@ import java.util.Locale; import java.util.Arrays; import java.util.Collections; +import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -985,4 +987,77 @@ public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object nod public static final class Self {} protected static Class self() { return Self.class; } + + /** + * Register a global {@link BindInterceptor} that uses {@link Descriptor#newInstance(StaplerRequest, JSONObject)} + * for instantiation + */ + @Initializer + public static void initGlobalBindInterceptor() { + boolean newInstance = WebApp.get(Jenkins.getInstance().servletContext).bindInterceptors.add(new BindInterceptor() { + final class Input { + final Class type; + final JSONObject json; + + private Input(Class type, JSONObject json) { + this.type = type; + this.json = json; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Input rhs = (Input) o; + return json==rhs.json && type==rhs.type; + + } + + @Override + public int hashCode() { + return 31*type.hashCode() + json.hashCode(); + } + } + + private final ThreadLocal> inputs = new ThreadLocal>() { + protected Stack initialValue() { + return new Stack(); + } + }; + + @Override + public Object instantiate(Class actualType, JSONObject json) { + if (Describable.class.isAssignableFrom(actualType)) { + Descriptor d = Jenkins.getInstance().getDescriptor(actualType); + if (d != null) { + try { + // only when Descriptor.newInstance is overridden + Method m = d.getClass().getMethod("newInstance", StaplerRequest.class, JSONObject.class); + if (m.getDeclaringClass() != Descriptor.class) { + Input newFrame = new Input(actualType,json); + if (!inputs.get().contains(newFrame)) { + // prevent infinite recursion in case Descriptor.newInstance calls right back into + // bindJSON + inputs.get().push(newFrame); + try { + StaplerRequest req = Stapler.getCurrentRequest(); + if (req != null) + return d.newInstance(req, json); + } finally { + inputs.get().pop(); + } + } + } + } catch (NoSuchMethodException e) { + throw new AssertionError(e); // this can't happen because Descriptor defines such a method + } catch (FormException e) { + throw new IllegalArgumentException(e); + } + } + } + return DEFAULT; + } + }); + } } diff --git a/test/src/test/java/hudson/model/DescriptorTest.java b/test/src/test/java/hudson/model/DescriptorTest.java index b8c0c5bc38be..8ce832e8598f 100644 --- a/test/src/test/java/hudson/model/DescriptorTest.java +++ b/test/src/test/java/hudson/model/DescriptorTest.java @@ -27,10 +27,17 @@ import hudson.model.Descriptor.PropertyType; import hudson.tasks.Shell; import static org.junit.Assert.*; + +import net.sf.json.JSONObject; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Bug; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.Stapler; +import org.kohsuke.stapler.StaplerRequest; + +import java.util.concurrent.Callable; public class DescriptorTest { @@ -51,4 +58,42 @@ public class DescriptorTest { } } + /** + * Make sure Descriptor.newInstance gets invoked. + */ + @Bug(18629) @Test + public void callDescriptor_newInstance() throws Exception { + rule.executeOnServer(new Callable() { + @Override + public Object call() throws Exception { + DataBoundBean v = Stapler.getCurrentRequest().bindJSON(DataBoundBean.class, new JSONObject()); + assertEquals(5,v.x); + return null; + } + }); + } + + public static class DataBoundBean extends AbstractDescribableImpl { + int x; + + // not @DataBoundConstructor + public DataBoundBean(int x) { + this.x = x; + } + + @TestExtension + public static class DescriptorImpl extends Descriptor { + @Override + public String getDisplayName() { + return ""; + } + + @Override + public DataBoundBean newInstance(StaplerRequest req, JSONObject formData) throws FormException { + return new DataBoundBean(5); + } + } + + } + }