diff --git a/cli/pom.xml b/cli/pom.xml index be7db1f9a1..b6771c7daf 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.94-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 9ce61b96c8..a4b053eee5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.94-SNAPSHOT jenkins-core diff --git a/core/src/main/java/hudson/XmlFile.java b/core/src/main/java/hudson/XmlFile.java index d5077b34db..16644262b9 100644 --- a/core/src/main/java/hudson/XmlFile.java +++ b/core/src/main/java/hudson/XmlFile.java @@ -24,10 +24,8 @@ package hudson; import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.StreamException; import com.thoughtworks.xstream.io.xml.Xpp3Driver; import hudson.diagnosis.OldDataMonitor; import hudson.model.Descriptor; @@ -148,7 +146,7 @@ public final class XmlFile { } try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()))) { return xs.fromXML(in); - } catch (XStreamException | Error | InvalidPathException e) { + } catch (RuntimeException | Error e) { throw new IOException("Unable to read "+file,e); } } @@ -165,7 +163,7 @@ public final class XmlFile { try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()))) { // TODO: expose XStream the driver from XStream return xs.unmarshal(DEFAULT_DRIVER.createReader(in), o); - } catch (XStreamException | Error | InvalidPathException e) { + } catch (RuntimeException | Error e) { throw new IOException("Unable to read "+file,e); } } @@ -184,7 +182,7 @@ public final class XmlFile { writing.set(null); } w.commit(); - } catch(StreamException e) { + } catch(RuntimeException e) { throw new IOException(e); } finally { w.abort(); diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index 3d0ee446d4..d75ec1b227 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -405,23 +405,16 @@ public class User extends AbstractModelObject implements AccessControlled, Descr * An existing or created user. May be {@code null} if a user does not exist and * {@code create} is false. */ - public static @Nullable User get(String idOrFullName, boolean create, Map context) { + public static @Nullable User get(String idOrFullName, boolean create, @Nonnull Map context) { if(idOrFullName==null) return null; - // sort resolvers by priority - List resolvers = new ArrayList(ExtensionList.lookup(CanonicalIdResolver.class)); - Collections.sort(resolvers); + // TODO: In many cases the method should receive the canonical ID. + // Maybe it makes sense to try AllUsers.byName().get(idkey) before invoking all resolvers and other stuff + // oleg-nenashev: FullNameResolver with User.getAll() loading and iteration makes me think it's a good idea. - String id = null; - for (CanonicalIdResolver resolver : resolvers) { - id = resolver.resolveCanonicalId(idOrFullName, context); - if (id != null) { - LOGGER.log(Level.FINE, "{0} mapped {1} to {2}", new Object[] {resolver, idOrFullName, id}); - break; - } - } + String id = CanonicalIdResolver.resolve(idOrFullName, context); // DefaultUserCanonicalIdResolver will always return a non-null id if all other CanonicalIdResolver failed if (id == null) { throw new IllegalStateException("The user id should be always non-null thanks to DefaultUserCanonicalIdResolver"); @@ -1081,6 +1074,16 @@ public class User extends AbstractModelObject implements AccessControlled, Descr } + /** + * Resolves User IDs by ID, full names or other strings. + * + * This extension point may be useful to map SCM user names to Jenkins {@link User} IDs. + * Currently the extension point is used in {@link User#get(String, boolean, Map)}. + * + * @since 1.479 + * @see jenkins.model.DefaultUserCanonicalIdResolver + * @see FullNameIdResolver + */ public static abstract class CanonicalIdResolver extends AbstractDescribableImpl implements ExtensionPoint, Comparable { /** @@ -1090,6 +1093,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr */ public static final String REALM = "realm"; + @Override public int compareTo(CanonicalIdResolver o) { // reverse priority order int i = getPriority(); @@ -1101,12 +1105,56 @@ public class User extends AbstractModelObject implements AccessControlled, Descr * extract user ID from idOrFullName with help from contextual infos. * can return null if no user ID matched the input */ - public abstract @CheckForNull String resolveCanonicalId(String idOrFullName, Map context); + public abstract @CheckForNull String resolveCanonicalId(String idOrFullName, @Nonnull Map context); + /** + * Gets priority of the resolver. + * Higher priority means that it will be checked earlier. + * + * Overriding methods must not use {@link Integer#MIN_VALUE}, because it will cause collisions + * with {@link jenkins.model.DefaultUserCanonicalIdResolver}. + * + * @return Priority of the resolver. + */ public int getPriority() { return 1; } + //TODO: It is too late to use Extension Point ordinals, right? + //Such sorting and collection rebuild is not good for User#get(...) method performance. + /** + * Gets all extension points, sorted by priority. + * @return Sorted list of extension point implementations. + * @since TODO + */ + public static List all() { + List resolvers = new ArrayList<>(ExtensionList.lookup(CanonicalIdResolver.class)); + Collections.sort(resolvers); + return resolvers; + } + + /** + * Resolves users using all available {@link CanonicalIdResolver}s. + * @param idOrFullName ID or full name of the user + * @param context Context + * @return Resolved User ID or {@code null} if the user ID cannot be resolved. + * @since TODO + */ + @CheckForNull + public static String resolve(@Nonnull String idOrFullName, @Nonnull Map context) { + for (CanonicalIdResolver resolver : CanonicalIdResolver.all()) { + //TODO: add try/catch for Runtime exceptions? It should not happen now && it may cause performance degradation + String id = resolver.resolveCanonicalId(idOrFullName, context); + if (id != null) { + LOGGER.log(Level.FINE, "{0} mapped {1} to {2}", new Object[] {resolver, idOrFullName, id}); + return id; + } + } + + // De-facto it is not going to happen OOTB, because the current DefaultUserCanonicalIdResolver + // always returns a value. But we still need to check nulls if somebody disables the extension point + return null; + } } diff --git a/core/src/test/java/hudson/util/AtomicFileWriterTest.java b/core/src/test/java/hudson/util/AtomicFileWriterTest.java index 716805bd31..51bd205cb4 100644 --- a/core/src/test/java/hudson/util/AtomicFileWriterTest.java +++ b/core/src/test/java/hudson/util/AtomicFileWriterTest.java @@ -1,5 +1,6 @@ package hudson.util; +import org.apache.commons.io.FileUtils; import org.hamcrest.core.StringContains; import org.junit.Before; import org.junit.Rule; @@ -21,6 +22,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class AtomicFileWriterTest { + private static final String PREVIOUS = "previous value \n blah"; @Rule public TemporaryFolder tmp = new TemporaryFolder(); File af; @@ -30,6 +32,7 @@ public class AtomicFileWriterTest { @Before public void setUp() throws IOException { af = tmp.newFile(); + FileUtils.writeStringToFile(af, PREVIOUS); afw = new AtomicFileWriter(af.toPath(), Charset.defaultCharset()); } @@ -56,25 +59,29 @@ public class AtomicFileWriterTest { public void writeToAtomicFile() throws Exception { // Given afw.write(expectedContent, 0, expectedContent.length()); + afw.write(expectedContent); + afw.write(' '); // When afw.flush(); // Then assertEquals("File writer did not properly flush to temporary file", - expectedContent.length(), Files.size(afw.getTemporaryPath())); + expectedContent.length()*2+1, Files.size(afw.getTemporaryPath())); } @Test public void commitToFile() throws Exception { // Given afw.write(expectedContent, 0, expectedContent.length()); + afw.write(new char[]{'h', 'e', 'y'}, 0, 3); // When afw.commit(); // Then - assertEquals(expectedContent.length(), Files.size(af.toPath())); + assertEquals(expectedContent.length()+3, Files.size(af.toPath())); + assertEquals(expectedContent+"hey", FileUtils.readFileToString(af)); } @Test @@ -87,8 +94,20 @@ public class AtomicFileWriterTest { // Then assertTrue(Files.notExists(afw.getTemporaryPath())); + assertEquals(PREVIOUS, FileUtils.readFileToString(af)); } + @Test + public void indexOutOfBoundsLeavesOriginalUntouched() throws Exception { + // Given + try { + afw.write(expectedContent, 0, expectedContent.length() + 10); + fail("exception expected"); + } catch (IndexOutOfBoundsException e) { + } + + assertEquals(PREVIOUS, FileUtils.readFileToString(af)); + } @Test public void badPath() throws Exception { final File newFile = tmp.newFile(); diff --git a/pom.xml b/pom.xml index fcd8b606e5..223c8a1ffc 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.94-SNAPSHOT pom Jenkins main module diff --git a/test/pom.xml b/test/pom.xml index 8e6f54d4b3..9dccd5a728 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.94-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 94f80d2274..3d7ea1e54e 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.94-SNAPSHOT jenkins-war diff --git a/war/src/main/js/pluginSetupWizardGui.js b/war/src/main/js/pluginSetupWizardGui.js index 923f6a3f27..2aac81ed33 100644 --- a/war/src/main/js/pluginSetupWizardGui.js +++ b/war/src/main/js/pluginSetupWizardGui.js @@ -889,16 +889,17 @@ var createPluginSetupWizard = function(appendTarget) { // ignore JSON parsing issues, this may be HTML } // we get 200 OK - var $page = $(data); + var responseText = data.responseText; + var $page = $(responseText); var $errors = $page.find('.error'); if($errors.length > 0) { var $main = $page.find('#main-panel').detach(); if($main.length > 0) { - data = data.replace(/body([^>]*)[>](.|[\r\n])+[<][/]body/,'body$1>'+$main.html()+']*)[>](.|[\r\n])+[<][/]body/,'body$1>'+$main.html()+'