* Enable testing against 2.107.1 in order to reveal potential JEP-200 regressions * #233 - DataBoundConfigurator now supports legacy constructors * Fix the merge conflict glitch * [Issue 233] - Introduce a LegacyDataBoundConstructorProvider to support some JCasC-incompatible API modification cases * Another merge conflict * Issue 233 - FindBugs
This commit is contained in:
parent
bdfbf5b1cd
commit
570a901a75
|
|
@ -1 +1 @@
|
|||
buildPlugin()
|
||||
buildPlugin(jenkinsVersions: [null, "2.107.1"])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.model.Descriptor;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.jenkinsci.plugins.casc.model.CNode;
|
||||
import org.jenkinsci.plugins.casc.model.Mapping;
|
||||
|
|
@ -11,9 +12,12 @@ import javax.annotation.Nonnull;
|
|||
import javax.annotation.PostConstruct;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static com.google.common.base.Defaults.defaultValue;
|
||||
|
|
@ -28,7 +32,7 @@ public class DataBoundConfigurator<T> extends BaseConfigurator<T> {
|
|||
|
||||
private final static Logger logger = Logger.getLogger(DataBoundConfigurator.class.getName());
|
||||
|
||||
private final Class target;
|
||||
private final Class<T> target;
|
||||
|
||||
public DataBoundConfigurator(Class<T> clazz) {
|
||||
this.target = clazz;
|
||||
|
|
@ -45,59 +49,38 @@ public class DataBoundConfigurator<T> extends BaseConfigurator<T> {
|
|||
// c can be null for component with no-arg constructor and no extra property to be set
|
||||
Mapping config = (c != null ? c.asMapping() : Mapping.EMPTY);
|
||||
|
||||
final Constructor constructor = getDataBoundConstructor();
|
||||
|
||||
final Parameter[] parameters = constructor.getParameters();
|
||||
final String[] names = ClassDescriptor.loadParameterNames(constructor);
|
||||
Object[] args = new Object[names.length];
|
||||
|
||||
if (parameters.length > 0) {
|
||||
// Many jenkins components haven't been migrated to @DataBoundSetter vs @NotNull constructor parameters
|
||||
// as a result it might be valid to reference a describable without parameters
|
||||
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
final CNode value = config.remove(names[i]);
|
||||
if (value == null && parameters[i].getAnnotation(Nonnull.class) != null) {
|
||||
throw new ConfiguratorException(names[i] + " is required to configure " + target);
|
||||
// TODO: The fallback resolution may end up resolving empty constructors
|
||||
// Ideally we need an annotation or whatever other logic that old constructors are acceptable
|
||||
final Constructor dataBoundConstructor = getDataBoundConstructor();
|
||||
T object = null;
|
||||
try {
|
||||
logger.log(Level.INFO, "Trying @DataBoundConstructor for target {0}: {1}",
|
||||
new Object[] {target, dataBoundConstructor} );
|
||||
object = tryConstructor((Constructor<T>) dataBoundConstructor, config);
|
||||
} catch (ConfiguratorException ex) {
|
||||
logger.log(Level.INFO, "Default databound constructor cannot be applied, " +
|
||||
"will consult with Legacy DataBoundConstructor providers", ex);
|
||||
for (Constructor constructor : LegacyDataBoundConstructorProvider.getLegacyDataBoundConstructors(target)) {
|
||||
if (constructor == dataBoundConstructor) {
|
||||
continue; // Already tried it
|
||||
}
|
||||
final Class t = parameters[i].getType();
|
||||
if (value != null) {
|
||||
if (Collection.class.isAssignableFrom(t)) {
|
||||
final Type pt = parameters[i].getParameterizedType();
|
||||
final Configurator lookup = Configurator.lookup(pt);
|
||||
logger.log(Level.INFO, "Trying legacy constructor {0} for target {1}",
|
||||
new Object[] {constructor, target});
|
||||
|
||||
final ArrayList<Object> list = new ArrayList<>();
|
||||
for (CNode o : value.asSequence()) {
|
||||
list.add(lookup.configure(o));
|
||||
}
|
||||
args[i] = list;
|
||||
try {
|
||||
object = tryConstructor((Constructor<T>) constructor, config);
|
||||
} catch (ConfiguratorException ex2) {
|
||||
logger.log(Level.INFO, "Constructor {0} didn't work for target {1}",
|
||||
new Object[] {constructor, target});
|
||||
}
|
||||
|
||||
} else {
|
||||
final Type pt = parameters[i].getParameterizedType();
|
||||
final Type k = pt != null ? pt : t;
|
||||
final Configurator configurator = Configurator.lookup(k);
|
||||
if (configurator == null) throw new IllegalStateException("No configurator implementation to manage "+k);
|
||||
args[i] = configurator.configure(value);
|
||||
}
|
||||
logger.info("Setting " + target + "." + names[i] + " = " + (value.isSensitiveData() ? "****" : value));
|
||||
} else if (t.isPrimitive()) {
|
||||
args[i] = defaultValue(t);
|
||||
if (object != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Object object;
|
||||
try {
|
||||
object = constructor.newInstance(args);
|
||||
} catch (IllegalArgumentException | InstantiationException | InvocationTargetException | IllegalAccessException ex) {
|
||||
List<String> argumentTypes = new ArrayList<>(args.length);
|
||||
for (Object arg : args) {
|
||||
argumentTypes.add(arg != null ? arg.getClass().getName() : "null");
|
||||
}
|
||||
throw new ConfiguratorException(this,
|
||||
"Failed to construct instance of " + target +
|
||||
".\n Constructor: " + constructor.toString() +
|
||||
".\n Arguments: " + argumentTypes, ex);
|
||||
if (object == null) {
|
||||
throw new ConfiguratorException("Failed to find a compatible constructor for target " + target);
|
||||
}
|
||||
|
||||
final Set<Attribute> attributes = describe();
|
||||
|
|
@ -136,7 +119,64 @@ public class DataBoundConfigurator<T> extends BaseConfigurator<T> {
|
|||
}
|
||||
}
|
||||
|
||||
return (T) object;
|
||||
return object;
|
||||
}
|
||||
|
||||
private T tryConstructor(Constructor<T> constructor, Mapping config) throws ConfiguratorException {
|
||||
final Parameter[] parameters = constructor.getParameters();
|
||||
final String[] names = ClassDescriptor.loadParameterNames(constructor);
|
||||
Object[] args = new Object[names.length];
|
||||
|
||||
if (parameters.length > 0) {
|
||||
// Many jenkins components haven't been migrated to @DataBoundSetter vs @NotNull constructor parameters
|
||||
// as a result it might be valid to reference a describable without parameters
|
||||
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
final CNode value = config.remove(names[i]);
|
||||
if (value == null && parameters[i].getAnnotation(Nonnull.class) != null) {
|
||||
throw new ConfiguratorException(names[i] + " is required to configure " + target);
|
||||
}
|
||||
final Class t = parameters[i].getType();
|
||||
if (value != null) {
|
||||
if (Collection.class.isAssignableFrom(t)) {
|
||||
final Type pt = parameters[i].getParameterizedType();
|
||||
final Configurator lookup = Configurator.lookup(pt);
|
||||
|
||||
final ArrayList<Object> list = new ArrayList<>();
|
||||
for (CNode o : value.asSequence()) {
|
||||
list.add(lookup.configure(o));
|
||||
}
|
||||
args[i] = list;
|
||||
|
||||
} else {
|
||||
final Type pt = parameters[i].getParameterizedType();
|
||||
final Type k = pt != null ? pt : t;
|
||||
final Configurator configurator = Configurator.lookup(k);
|
||||
if (configurator == null) throw new ConfiguratorException("No configurator implementation to manage "+k);
|
||||
args[i] = configurator.configure(value);
|
||||
}
|
||||
logger.info("Setting " + target + "." + names[i] + " = " + (value.isSensitiveData() ? "****" : value));
|
||||
} else if (t.isPrimitive()) {
|
||||
args[i] = defaultValue(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final T object;
|
||||
try {
|
||||
object = constructor.newInstance(args);
|
||||
} catch (IllegalArgumentException | InstantiationException | InvocationTargetException | IllegalAccessException ex) {
|
||||
List<String> argumentTypes = new ArrayList<>(args.length);
|
||||
for (Object arg : args) {
|
||||
argumentTypes.add(arg != null ? arg.getClass().getName() : "null");
|
||||
}
|
||||
throw new ConfiguratorException(this,
|
||||
"Failed to construct instance of " + target +
|
||||
".\n Constructor: " + constructor.toString() +
|
||||
".\n Arguments: " + argumentTypes, ex);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2018 CloudBees, Inc.
|
||||
*
|
||||
* 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 org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.ExtensionList;
|
||||
import hudson.ExtensionPoint;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.Beta;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides resolution logic for Legacy {@link org.kohsuke.stapler.DataBoundConstructor}s.
|
||||
* This extension point exists to support explicit specifications of compatible constructors if
|
||||
* the annotated one does not work.
|
||||
*
|
||||
* This extension point can be used as a workaround for some cases when there
|
||||
* is a need to modify {@link org.kohsuke.stapler.DataBoundConstructor} in
|
||||
* a way not compatible with JCasC (or to support existing modifications).
|
||||
* It <b>should not</b> be used to defined for defining custom initialization logic,
|
||||
* new {@link Configurator}s should be created instead.
|
||||
*
|
||||
* @see DataBoundConfigurator
|
||||
* @author Oleg Nenashev
|
||||
* @since TODO
|
||||
*/
|
||||
@Restricted(Beta.class)
|
||||
public abstract class LegacyDataBoundConstructorProvider<TTarget> implements ExtensionPoint {
|
||||
|
||||
@Nonnull
|
||||
public abstract Set<Constructor<TTarget>> getConstructorsFor(@Nonnull Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Locates legacy {@link org.kohsuke.stapler.DataBoundConstructor}s
|
||||
* @param targetClazz Target class which should be produced by constructors
|
||||
* @param <T> Target class which should be produced by constructors
|
||||
* @return List of Legacy constructors defined by extension points.
|
||||
* The list is ordered depending on Extension point ordinals.
|
||||
*/
|
||||
@Nonnull
|
||||
public static <T> Set<Constructor<T>> getLegacyDataBoundConstructors(@Nonnull Class<T> targetClazz) {
|
||||
HashSet<Constructor<T>> constructors = new HashSet<>();
|
||||
for (LegacyDataBoundConstructorProvider<?> provider : all()) {
|
||||
final Set<? extends Constructor<?>> provided = provider.getConstructorsFor(targetClazz);
|
||||
for (Constructor<?> pr : provided) {
|
||||
if (targetClazz.isAssignableFrom(pr.getDeclaringClass())) {
|
||||
constructors.add((Constructor<T>) pr);
|
||||
} else {
|
||||
throw new IllegalStateException("Extension " + provider + " provided a wrong constructor type for " + targetClazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
return constructors;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static ExtensionList<LegacyDataBoundConstructorProvider> all() {
|
||||
return ExtensionList.lookup(LegacyDataBoundConstructorProvider.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2018 CloudBees, Inc.
|
||||
*
|
||||
* 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 org.jenkinsci.plugins.casc.core;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.slaves.JNLPLauncher;
|
||||
import org.jenkinsci.plugins.casc.LegacyDataBoundConstructorProvider;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides information about Legacy constructors for {@link JNLPLauncher}.
|
||||
* @see org.jenkinsci.plugins.casc.DataBoundConfigurator
|
||||
* @author Oleg Nenashev
|
||||
* @since TODO
|
||||
*/
|
||||
@Extension(optional = true)
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class JNLPLauncherLegacyConstructorProvider
|
||||
extends LegacyDataBoundConstructorProvider<JNLPLauncher> {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Set<Constructor<JNLPLauncher>> getConstructorsFor(@Nonnull Class<?> clazz) {
|
||||
if (!clazz.equals(JNLPLauncher.class)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
try {
|
||||
return Collections.singleton(JNLPLauncher.class.getConstructor(
|
||||
String.class, String.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Deprecated Method has been removed or so, we do not care here
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue