diff --git a/Jenkinsfile b/Jenkinsfile index 36135cf4a4..c759de5733 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ import com.cloudogu.ces.cesbuildlib.* node('docker') { // Change this as when we go back to default - necessary for proper SonarQube analysis - mainBranch = '2.0.0-m3' + mainBranch = 'default' properties([ // Keep only the last 10 build to preserve space @@ -37,7 +37,7 @@ node('docker') { } stage('Integration Test') { - mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true' + mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true -DClassLoaderLeakPreventor.threadWaitMs=10' } stage('SonarQube') { diff --git a/package.json b/package.json index b2f14826c5..8d9a9c931b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test": "lerna run --scope '@scm-manager/ui-*' test", "typecheck": "lerna run --scope '@scm-manager/ui-*' typecheck", "serve": "webpack-dev-server --mode=development --config=scm-ui/ui-scripts/src/webpack.config.js", - "deploy": "ui-scripts publish" + "deploy": "ui-scripts publish", + "set-version": "ui-scripts version" }, "devDependencies": { "babel-plugin-reflow": "^0.2.7", diff --git a/pom.xml b/pom.xml index 910287abf0..a27dcdb44e 100644 --- a/pom.xml +++ b/pom.xml @@ -220,7 +220,13 @@ org.jboss.resteasy - resteasy-jaxrs + resteasy-core + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-core-spi ${resteasy.version} @@ -444,7 +450,7 @@ sonia.scm.maven smp-maven-plugin - 1.0.0-alpha-8 + 1.0.0-rc1 @@ -831,7 +837,7 @@ 3.0.1 2.1.1 - 3.6.2.Final + 4.4.1.Final 1.19.4 2.11.1 2.10.0 @@ -839,7 +845,7 @@ 2.3.0 - 1.5.1 + 1.6.1 9.4.22.v20191022 diff --git a/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java b/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java index c548f30d45..38961ccaf8 100644 --- a/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java +++ b/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java @@ -99,8 +99,9 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import static javax.lang.model.util.ElementFilter.methodsIn; + /** - * * @author Sebastian Sdorra */ @SupportedAnnotationTypes("*") @@ -372,6 +373,18 @@ public final class ScmAnnotationProcessor extends AbstractProcessor { attributes.put(entry.getKey().getSimpleName().toString(), getValue(entry.getValue())); } + + // add default values + for (ExecutableElement meth : methodsIn(annotationMirror.getAnnotationType().asElement().getEnclosedElements())) { + String attribute = meth.getSimpleName().toString(); + AnnotationValue defaultValue = meth.getDefaultValue(); + if (defaultValue != null && !attributes.containsKey(attribute)) { + String value = getValue(defaultValue); + if (value != null && !value.isEmpty()) { + attributes.put(attribute, value); + } + } + } } } diff --git a/scm-annotations/pom.xml b/scm-annotations/pom.xml index ba8922e0b6..c5a892af95 100644 --- a/scm-annotations/pom.xml +++ b/scm-annotations/pom.xml @@ -8,8 +8,7 @@ scm 2.0.0-SNAPSHOT - - sonia.scm + scm-annotations 2.0.0-SNAPSHOT scm-annotations diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 8437b256b2..4e8e315253 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -98,7 +98,7 @@ org.jboss.resteasy - resteasy-jaxrs + resteasy-core test diff --git a/scm-core/src/main/java/sonia/scm/SCMContext.java b/scm-core/src/main/java/sonia/scm/SCMContext.java index 42d311ed2a..f5367625bb 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContext.java +++ b/scm-core/src/main/java/sonia/scm/SCMContext.java @@ -59,7 +59,7 @@ public final class SCMContext */ public static final User ANONYMOUS = new User(USER_ANONYMOUS, "SCM Anonymous", - "scm-anonymous@scm-manager.com"); + "scm-anonymous@scm-manager.org"); /** Singleton instance of {@link SCMContextProvider} */ private static volatile SCMContextProvider provider; diff --git a/scm-core/src/main/java/sonia/scm/collect/IterableQueue.java b/scm-core/src/main/java/sonia/scm/collect/IterableQueue.java index 9d3c85bb90..6b6af9fd0f 100644 --- a/scm-core/src/main/java/sonia/scm/collect/IterableQueue.java +++ b/scm-core/src/main/java/sonia/scm/collect/IterableQueue.java @@ -134,7 +134,7 @@ public final class IterableQueue implements Iterable else { logger.trace("create queue iterator"); - iterator = new QueueIterator(this); + iterator = new QueueIterator<>(this); } return iterator; diff --git a/scm-core/src/main/java/sonia/scm/collect/LimitedSortedSet.java b/scm-core/src/main/java/sonia/scm/collect/LimitedSortedSet.java index fe99ee7a1c..681b8ef07c 100644 --- a/scm-core/src/main/java/sonia/scm/collect/LimitedSortedSet.java +++ b/scm-core/src/main/java/sonia/scm/collect/LimitedSortedSet.java @@ -63,7 +63,7 @@ public class LimitedSortedSet extends ForwardingSortedSet */ public LimitedSortedSet(int maxSize) { - this.sortedSet = new TreeSet(); + this.sortedSet = new TreeSet<>(); this.maxSize = maxSize; } diff --git a/scm-core/src/main/java/sonia/scm/io/AbstractResourceProcessor.java b/scm-core/src/main/java/sonia/scm/io/AbstractResourceProcessor.java index 8b5c1d1dd7..6c00a319c9 100644 --- a/scm-core/src/main/java/sonia/scm/io/AbstractResourceProcessor.java +++ b/scm-core/src/main/java/sonia/scm/io/AbstractResourceProcessor.java @@ -183,5 +183,5 @@ public abstract class AbstractResourceProcessor implements ResourceProcessor //~--- fields --------------------------------------------------------------- /** Field description */ - private Map variableMap = new HashMap(); + private Map variableMap = new HashMap<>(); } diff --git a/scm-core/src/main/java/sonia/scm/io/INIConfiguration.java b/scm-core/src/main/java/sonia/scm/io/INIConfiguration.java index 9f3cce03f6..c9695b7ca6 100644 --- a/scm-core/src/main/java/sonia/scm/io/INIConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/io/INIConfiguration.java @@ -52,7 +52,7 @@ public class INIConfiguration */ public INIConfiguration() { - this.sectionMap = new LinkedHashMap(); + this.sectionMap = new LinkedHashMap<>(); } //~--- methods -------------------------------------------------------------- diff --git a/scm-core/src/main/java/sonia/scm/io/INISection.java b/scm-core/src/main/java/sonia/scm/io/INISection.java index 03768a84ca..0ce6ea6bcf 100644 --- a/scm-core/src/main/java/sonia/scm/io/INISection.java +++ b/scm-core/src/main/java/sonia/scm/io/INISection.java @@ -55,7 +55,7 @@ public class INISection public INISection(String name) { this.name = name; - this.parameters = new LinkedHashMap(); + this.parameters = new LinkedHashMap<>(); } /** diff --git a/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java b/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java index db8d96ca15..c05e970e50 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java +++ b/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java @@ -25,7 +25,7 @@ public class AvailablePlugin implements Plugin { return pending; } - public AvailablePlugin install() { + AvailablePlugin install() { Preconditions.checkState(!pending, "installation is already pending"); return new AvailablePlugin(pluginDescriptor, true); } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java index 1f1e7cfefb..63d4638ab8 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java @@ -113,7 +113,6 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler Repository repository = new Repository(); repository.setName(repositoryName); - repository.setPublicReadable(false); repository.setType(getTypeName()); return repository; diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 463085a7ea..e35e12bbb0 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -83,8 +83,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per private String name; @XmlElement(name = "permission") private Set permissions = new HashSet<>(); - @XmlElement(name = "public") - private boolean publicReadable = false; private String type; @@ -225,15 +223,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return Util.isEmpty(healthCheckFailures); } - /** - * Returns true if the {@link Repository} is public readable. - * - * @return true if the {@link Repository} is public readable - */ - public boolean isPublicReadable() { - return publicReadable; - } - /** * Returns true if the {@link Repository} is valid. *
    @@ -292,10 +281,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return this.permissions.remove(permission); } - public void setPublicReadable(boolean publicReadable) { - this.publicReadable = publicReadable; - } - public void setType(String type) { this.type = type; } @@ -332,7 +317,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per repository.setLastModified(lastModified); repository.setDescription(description); repository.setPermissions(permissions); - repository.setPublicReadable(publicReadable); // do not copy health check results } @@ -360,7 +344,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per && Objects.equal(name, other.name) && Objects.equal(contact, other.contact) && Objects.equal(description, other.description) - && Objects.equal(publicReadable, other.publicReadable) && Objects.equal(permissions, other.permissions) && Objects.equal(type, other.type) && Objects.equal(creationDate, other.creationDate) @@ -371,7 +354,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per @Override public int hashCode() { - return Objects.hashCode(id, namespace, name, contact, description, publicReadable, + return Objects.hashCode(id, namespace, name, contact, description, permissions, type, creationDate, lastModified, properties, healthCheckFailures); } @@ -384,7 +367,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per .add("name", name) .add("contact", contact) .add("description", description) - .add("publicReadable", publicReadable) .add("permissions", permissions) .add("type", type) .add("lastModified", lastModified) diff --git a/scm-core/src/main/java/sonia/scm/util/Base32.java b/scm-core/src/main/java/sonia/scm/util/Base32.java index cadbab3d8f..5ff3576078 100644 --- a/scm-core/src/main/java/sonia/scm/util/Base32.java +++ b/scm-core/src/main/java/sonia/scm/util/Base32.java @@ -46,7 +46,7 @@ public final class Base32 extends AbstractBase { /** base value */ - private static final BigInteger BASE = BigInteger.valueOf(32l); + private static final BigInteger BASE = BigInteger.valueOf(32L); /** char table */ private static final String CHARS = "0123456789bcdefghjkmnpqrstuvwxyz"; diff --git a/scm-core/src/main/java/sonia/scm/util/Base62.java b/scm-core/src/main/java/sonia/scm/util/Base62.java index 04fd1657d6..b9c9fd6a54 100644 --- a/scm-core/src/main/java/sonia/scm/util/Base62.java +++ b/scm-core/src/main/java/sonia/scm/util/Base62.java @@ -46,7 +46,7 @@ public final class Base62 extends AbstractBase { /** base value */ - private static final BigInteger BASE = BigInteger.valueOf(62l); + private static final BigInteger BASE = BigInteger.valueOf(62L); /** char table */ private static final String CHARS = diff --git a/scm-core/src/main/java/sonia/scm/util/LinkTextParser.java b/scm-core/src/main/java/sonia/scm/util/LinkTextParser.java index 2037beeeff..67ca800bcb 100644 --- a/scm-core/src/main/java/sonia/scm/util/LinkTextParser.java +++ b/scm-core/src/main/java/sonia/scm/util/LinkTextParser.java @@ -79,7 +79,7 @@ public final class LinkTextParser public static String parseText(String content) { Matcher m = REGEX_URL.matcher(content); - List tokens = new ArrayList(); + List tokens = new ArrayList<>(); int position = 0; String tokenContent = null; diff --git a/scm-core/src/main/java/sonia/scm/util/ServiceUtil.java b/scm-core/src/main/java/sonia/scm/util/ServiceUtil.java index 138345bb05..04bdfb6dea 100644 --- a/scm-core/src/main/java/sonia/scm/util/ServiceUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/ServiceUtil.java @@ -119,7 +119,7 @@ public final class ServiceUtil */ public static List getServices(Class type) { - List result = new ArrayList(); + List result = new ArrayList<>(); try { diff --git a/scm-core/src/main/java/sonia/scm/util/Util.java b/scm-core/src/main/java/sonia/scm/util/Util.java index 5439cc77ec..cb64b75aff 100644 --- a/scm-core/src/main/java/sonia/scm/util/Util.java +++ b/scm-core/src/main/java/sonia/scm/util/Util.java @@ -38,23 +38,12 @@ package sonia.scm.util; import com.google.common.base.Strings; import com.google.common.collect.Multimap; -//~--- JDK imports ------------------------------------------------------------ - import java.math.BigInteger; - import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; +//~--- JDK imports ------------------------------------------------------------ /** * @@ -273,12 +262,12 @@ public final class Util Comparator comparator, CollectionAppender appender, int start, int limit) { - List result = new ArrayList(); - List valueList = new ArrayList(values); + List result = new ArrayList<>(); + List valueList = new ArrayList<>(values); if (comparator != null) { - Collections.sort(valueList, comparator); + valueList.sort(comparator); } int length = valueList.size(); @@ -506,12 +495,10 @@ public final class Util { StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < byteValue.length; i++) - { - int x = byteValue[i] & 0xff; + for (final byte aByteValue : byteValue) { + int x = aByteValue & 0xff; - if (x < 16) - { + if (x < 16) { buffer.append('0'); } diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java b/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java index 796d6125d8..541ae9ba23 100644 --- a/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java +++ b/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java @@ -66,7 +66,7 @@ public class EnvList */ public EnvList() { - envMap = new HashMap(); + envMap = new HashMap<>(); } /** @@ -77,7 +77,7 @@ public class EnvList */ public EnvList(EnvList l) { - envMap = new HashMap(l.envMap); + envMap = new HashMap<>(l.envMap); } //~--- methods -------------------------------------------------------------- diff --git a/scm-core/src/main/java/sonia/scm/web/filter/BufferedHttpServletResponse.java b/scm-core/src/main/java/sonia/scm/web/filter/BufferedHttpServletResponse.java index b91f2b62c9..ff174c4fe8 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/BufferedHttpServletResponse.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/BufferedHttpServletResponse.java @@ -470,13 +470,13 @@ public class BufferedHttpServletResponse extends HttpServletResponseWrapper private ByteArrayPrintWriter pw = null; /** Field description */ - private Set cookies = new HashSet(); + private Set cookies = new HashSet<>(); /** Field description */ private int statusCode = HttpServletResponse.SC_OK; /** Field description */ - private Map headers = new LinkedHashMap(); + private Map headers = new LinkedHashMap<>(); /** Field description */ private String statusMessage; diff --git a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java index 1d865dc4bc..06059c2926 100644 --- a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java +++ b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java @@ -40,28 +40,20 @@ import com.google.common.io.ByteStreams; import com.google.common.io.Closer; import com.google.inject.Inject; import com.google.inject.Singleton; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; import java.net.HttpURLConnection; - import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +//~--- JDK imports ------------------------------------------------------------ /** * @@ -175,10 +167,8 @@ public class ProxyServlet extends HttpServlet private void copyContent(HttpURLConnection con, HttpServletResponse response) throws IOException { - Closer closer = Closer.create(); - try - { + try (Closer closer = Closer.create()) { InputStream webToProxyBuf = closer.register(new BufferedInputStream(con.getInputStream())); OutputStream proxyToClientBuf = @@ -188,10 +178,6 @@ public class ProxyServlet extends HttpServlet logger.trace("copied {} bytes for proxy", bytes); } - finally - { - closer.close(); - } } /** diff --git a/scm-core/src/main/java/sonia/scm/xml/XmlMapStringAdapter.java b/scm-core/src/main/java/sonia/scm/xml/XmlMapStringAdapter.java index ab6cd83ccc..04d04fae60 100644 --- a/scm-core/src/main/java/sonia/scm/xml/XmlMapStringAdapter.java +++ b/scm-core/src/main/java/sonia/scm/xml/XmlMapStringAdapter.java @@ -102,7 +102,7 @@ public class XmlMapStringAdapter public Map unmarshal(XmlMapStringElement[] elements) throws Exception { - Map map = new HashMap(); + Map map = new HashMap<>(); if (elements != null) { diff --git a/scm-core/src/main/java/sonia/scm/xml/XmlSetStringAdapter.java b/scm-core/src/main/java/sonia/scm/xml/XmlSetStringAdapter.java index 708159bfac..8d088aa205 100644 --- a/scm-core/src/main/java/sonia/scm/xml/XmlSetStringAdapter.java +++ b/scm-core/src/main/java/sonia/scm/xml/XmlSetStringAdapter.java @@ -90,7 +90,7 @@ public class XmlSetStringAdapter extends XmlAdapter> @Override public Set unmarshal(String rawString) throws Exception { - Set tokens = new HashSet(); + Set tokens = new HashSet<>(); for (String token : rawString.split(",")) { diff --git a/scm-core/src/test/java/sonia/scm/collect/IterableQueueTest.java b/scm-core/src/test/java/sonia/scm/collect/IterableQueueTest.java index ade4f3247b..130924fcd4 100644 --- a/scm-core/src/test/java/sonia/scm/collect/IterableQueueTest.java +++ b/scm-core/src/test/java/sonia/scm/collect/IterableQueueTest.java @@ -63,7 +63,7 @@ public class IterableQueueTest @Test(expected = IllegalStateException.class) public void testDuplicatedEndReached() { - IterableQueue queue = new IterableQueue(); + IterableQueue queue = new IterableQueue<>(); queue.endReached(); queue.endReached(); @@ -76,7 +76,7 @@ public class IterableQueueTest @Test public void testIterator() { - IterableQueue queue = new IterableQueue(); + IterableQueue queue = new IterableQueue<>(); assertEquals(QueueIterator.class, queue.iterator().getClass()); queue.endReached(); @@ -120,7 +120,7 @@ public class IterableQueueTest @Test(expected = IllegalStateException.class) public void testPushEndReached() { - IterableQueue queue = new IterableQueue(); + IterableQueue queue = new IterableQueue<>(); queue.push("a"); queue.endReached(); @@ -134,7 +134,7 @@ public class IterableQueueTest @Test public void testSingleConsumer() { - final IterableQueue queue = new IterableQueue(); + final IterableQueue queue = new IterableQueue<>(); new Thread(new IntegerProducer(queue, false, 100)).start(); assertResult(Lists.newArrayList(queue), 100); @@ -176,12 +176,12 @@ public class IterableQueueTest ExecutorService executor = Executors.newFixedThreadPool(threads); List>> futures = Lists.newArrayList(); - final IterableQueue queue = new IterableQueue(); + final IterableQueue queue = new IterableQueue<>(); for (int i = 0; i < consumer; i++) { Future> future = - executor.submit(new CallableQueueCollector(queue)); + executor.submit(new CallableQueueCollector<>(queue)); futures.add(future); } diff --git a/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java b/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java index 45c490d15e..1629562132 100644 --- a/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java +++ b/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java @@ -71,8 +71,8 @@ public class ScmModuleTest assertThat( module.getExtensions(), containsInAnyOrder( - (Class) String.class, - (Class) Integer.class + String.class, + Integer.class ) ); assertThat( @@ -86,8 +86,8 @@ public class ScmModuleTest assertThat( module.getEvents(), containsInAnyOrder( - (Class) String.class, - (Class) Boolean.class + String.class, + Boolean.class ) ); assertThat( @@ -100,15 +100,15 @@ public class ScmModuleTest assertThat( module.getRestProviders(), containsInAnyOrder( - (Class) Integer.class, - (Class) Long.class + Integer.class, + Long.class ) ); assertThat( module.getRestResources(), containsInAnyOrder( - (Class) Float.class, - (Class) Double.class + Float.class, + Double.class ) ); //J+ diff --git a/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java b/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java index b676b4bbaf..775f9f0814 100644 --- a/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java +++ b/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java @@ -128,7 +128,7 @@ public class TemplateEngineFactoryTest assertTrue(engines.contains(engine1)); assertTrue(engines.contains(engine2)); - Set ce = new HashSet(); + Set ce = new HashSet<>(); ce.add(engine1); factory = new TemplateEngineFactory(ce, engine2); diff --git a/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java b/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java index 16d4b59ffc..eb0af7926c 100644 --- a/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java +++ b/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java @@ -56,7 +56,7 @@ public class UrlBuilderTest UrlBuilder builder = new UrlBuilder("http://www.short.de"); builder.appendParameter("i", 123).appendParameter("s", "abc"); - builder.appendParameter("b", true).appendParameter("l", 321l); + builder.appendParameter("b", true).appendParameter("l", 321L); assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321", builder.toString()); builder.appendParameter("c", "a b"); assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321&c=a%20b", builder.toString()); diff --git a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupDatabase.java index 431d53594a..e4d436283c 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupDatabase.java +++ b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupDatabase.java @@ -199,7 +199,7 @@ public class XmlGroupDatabase implements XmlDatabase /** Field description */ @XmlJavaTypeAdapter(XmlGroupMapAdapter.class) @XmlElement(name = "groups") - private Map groupMap = new LinkedHashMap(); + private Map groupMap = new LinkedHashMap<>(); /** Field description */ private Long lastModified; diff --git a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupList.java b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupList.java index da14968d30..c889944fc4 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupList.java +++ b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupList.java @@ -72,7 +72,7 @@ public class XmlGroupList implements Iterable */ public XmlGroupList(Map groupMap) { - this.groups = new LinkedList(groupMap.values()); + this.groups = new LinkedList<>(groupMap.values()); } //~--- methods -------------------------------------------------------------- diff --git a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupMapAdapter.java b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupMapAdapter.java index b79c8836d2..430d0a84a8 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupMapAdapter.java +++ b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupMapAdapter.java @@ -81,7 +81,7 @@ public class XmlGroupMapAdapter @Override public Map unmarshal(XmlGroupList groups) throws Exception { - Map groupMap = new LinkedHashMap(); + Map groupMap = new LinkedHashMap<>(); for (Group group : groups) { diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java index f22180ff9a..3a81f54fb2 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java @@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.ContextEntry; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; +import sonia.scm.store.CopyOnWrite; import sonia.scm.store.StoreConstants; import sonia.scm.update.UpdateStepRepositoryMetadataAccess; @@ -43,7 +44,10 @@ public class MetadataStore implements UpdateStepRepositoryMetadataAccess { try { Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - marshaller.marshal(repository, resolveDataPath(path).toFile()); + CopyOnWrite.withTemporaryFile( + temp -> marshaller.marshal(repository, temp.toFile()), + resolveDataPath(path) + ); } catch (JAXBException ex) { throw new InternalRepositoryException(repository, "failed write repository metadata", ex); } diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java index 70698aed59..c895b69840 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.ContextEntry; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.store.CopyOnWrite; import sonia.scm.xml.IndentXMLStreamWriter; import sonia.scm.xml.XmlStreams; @@ -40,23 +41,28 @@ class PathDatabase { ensureParentDirectoryExists(); LOG.trace("write repository path database to {}", storePath); - try (IndentXMLStreamWriter writer = XmlStreams.createWriter(storePath)) { - writer.writeStartDocument(ENCODING, VERSION); + CopyOnWrite.withTemporaryFile( + temp -> { + try (IndentXMLStreamWriter writer = XmlStreams.createWriter(temp)) { + writer.writeStartDocument(ENCODING, VERSION); - writeRepositoriesStart(writer, creationTime, lastModified); - for (Map.Entry e : pathDatabase.entrySet()) { - writeRepository(writer, e.getKey(), e.getValue()); - } - writer.writeEndElement(); + writeRepositoriesStart(writer, creationTime, lastModified); + for (Map.Entry e : pathDatabase.entrySet()) { + writeRepository(writer, e.getKey(), e.getValue()); + } + writer.writeEndElement(); - writer.writeEndDocument(); - } catch (XMLStreamException | IOException ex) { - throw new InternalRepositoryException( - ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(), - "failed to write repository path database", - ex - ); - } + writer.writeEndDocument(); + } catch (XMLStreamException | IOException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(), + "failed to write repository path database", + ex + ); + } + }, + storePath + ); } private void ensureParentDirectoryExists() { diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java new file mode 100644 index 0000000000..135221ea82 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java @@ -0,0 +1,112 @@ +package sonia.scm.store; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public final class CopyOnWrite { + + private static final Logger LOG = LoggerFactory.getLogger(CopyOnWrite.class); + + private CopyOnWrite() {} + + public static void withTemporaryFile(FileWriter writer, Path targetFile) { + validateInput(targetFile); + Path temporaryFile = createTemporaryFile(targetFile); + executeCallback(writer, targetFile, temporaryFile); + replaceOriginalFile(targetFile, temporaryFile); + } + + @SuppressWarnings("squid:S3725") // performance of Files#isDirectory + private static void validateInput(Path targetFile) { + if (Files.isDirectory(targetFile)) { + throw new IllegalArgumentException("target file has to be a regular file, not a directory"); + } + if (targetFile.getParent() == null) { + throw new IllegalArgumentException("target file has to be specified with a parent directory"); + } + } + + private static Path createTemporaryFile(Path targetFile) { + Path temporaryFile = targetFile.getParent().resolve(UUID.randomUUID().toString()); + try { + Files.createFile(temporaryFile); + } catch (IOException ex) { + LOG.error("Error creating temporary file {} to replace file {}", temporaryFile, targetFile); + throw new StoreException("could not create temporary file", ex); + } + return temporaryFile; + } + + private static void executeCallback(FileWriter writer, Path targetFile, Path temporaryFile) { + try { + writer.write(temporaryFile); + } catch (RuntimeException e) { + throw e; + } catch (Exception ex) { + LOG.error("Error writing to temporary file {}. Target file {} has not been modified", temporaryFile, targetFile); + throw new StoreException("could not write temporary file", ex); + } + } + + private static void replaceOriginalFile(Path targetFile, Path temporaryFile) { + Path backupFile = backupOriginalFile(targetFile); + try { + Files.move(temporaryFile, targetFile); + } catch (IOException e) { + LOG.error("Error renaming temporary file {} to target file {}", temporaryFile, targetFile); + restoreBackup(targetFile, backupFile); + throw new StoreException("could rename temporary file to target file", e); + } + deleteBackupFile(backupFile); + } + + @SuppressWarnings("squid:S3725") // performance of Files#exists + private static Path backupOriginalFile(Path targetFile) { + Path directory = targetFile.getParent(); + if (Files.exists(targetFile)) { + Path backupFile = directory.resolve(UUID.randomUUID().toString()); + try { + Files.move(targetFile, backupFile); + } catch (IOException e) { + LOG.error("Could not backup original file {}. Aborting here so that original file will not be overwritten.", targetFile); + throw new StoreException("could not create backup of file", e); + } + return backupFile; + } else { + return null; + } + } + + private static void deleteBackupFile(Path backupFile) { + if (backupFile != null) { + try { + Files.delete(backupFile); + } catch (IOException e) { + LOG.warn("Could not delete backup file {}", backupFile); + throw new StoreException("could not delete backup file", e); + } + } + } + + private static void restoreBackup(Path targetFile, Path backupFile) { + if (backupFile != null) { + try { + Files.move(backupFile, targetFile); + LOG.info("Recovered original file {} from backup", targetFile); + } catch (IOException e) { + LOG.error("Could not replace original file {} with backup file {} after failure", targetFile, backupFile); + } + } + } + + @FunctionalInterface + public interface FileWriter { + @SuppressWarnings("squid:S00112") // We do not want to limit exceptions here + void write(Path t) throws Exception; + } +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java index 40cf03c8a8..deddaa9500 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java @@ -317,48 +317,47 @@ public class JAXBConfigurationEntryStore implements ConfigurationEntryStore { + try (IndentXMLStreamWriter writer = XmlStreams.createWriter(temp)) { + writer.writeStartDocument(); - // configuration start - writer.writeStartElement(TAG_CONFIGURATION); + // configuration start + writer.writeStartElement(TAG_CONFIGURATION); - Marshaller m = context.createMarshaller(); + Marshaller m = context.createMarshaller(); - m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); - for (Entry e : entries.entrySet()) - { + for (Entry e : entries.entrySet()) { - // entry start - writer.writeStartElement(TAG_ENTRY); + // entry start + writer.writeStartElement(TAG_ENTRY); - // key start - writer.writeStartElement(TAG_KEY); - writer.writeCharacters(e.getKey()); + // key start + writer.writeStartElement(TAG_KEY); + writer.writeCharacters(e.getKey()); - // key end - writer.writeEndElement(); + // key end + writer.writeEndElement(); - // value - JAXBElement je = new JAXBElement(QName.valueOf(TAG_VALUE), type, - e.getValue()); + // value + JAXBElement je = new JAXBElement<>(QName.valueOf(TAG_VALUE), type, + e.getValue()); - m.marshal(je, writer); + m.marshal(je, writer); - // entry end - writer.writeEndElement(); - } + // entry end + writer.writeEndElement(); + } - // configuration end - writer.writeEndElement(); - writer.writeEndDocument(); - } - catch (Exception ex) - { - throw new StoreException("could not store configuration", ex); - } + // configuration end + writer.writeEndElement(); + writer.writeEndDocument(); + } + }, + file.toPath() + ); } //~--- fields --------------------------------------------------------------- diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java index ac1477d7ea..92bd0fbcc4 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java @@ -113,7 +113,10 @@ public class JAXBConfigurationStore extends AbstractStore { Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - marshaller.marshal(object, configFile); + CopyOnWrite.withTemporaryFile( + temp -> marshaller.marshal(object, temp.toFile()), + configFile.toPath() + ); } catch (JAXBException ex) { throw new StoreException("failed to marshall object", ex); diff --git a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserDatabase.java index 579856fce7..2306fd588b 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserDatabase.java +++ b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserDatabase.java @@ -202,5 +202,5 @@ public class XmlUserDatabase implements XmlDatabase /** Field description */ @XmlJavaTypeAdapter(XmlUserMapAdapter.class) @XmlElement(name = "users") - private Map userMap = new LinkedHashMap(); + private Map userMap = new LinkedHashMap<>(); } diff --git a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserList.java b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserList.java index e76f008b92..6877d8544e 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserList.java +++ b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserList.java @@ -72,7 +72,7 @@ public class XmlUserList implements Iterable */ public XmlUserList(Map userMap) { - this.users = new LinkedList(userMap.values()); + this.users = new LinkedList<>(userMap.values()); } //~--- methods -------------------------------------------------------------- diff --git a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserMapAdapter.java b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserMapAdapter.java index f5573310d5..0c2386f37c 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserMapAdapter.java +++ b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserMapAdapter.java @@ -81,7 +81,7 @@ public class XmlUserMapAdapter @Override public Map unmarshal(XmlUserList users) throws Exception { - Map userMap = new LinkedHashMap(); + Map userMap = new LinkedHashMap<>(); for (User user : users) { diff --git a/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java b/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java index 4b3d9b0f28..c8c5240f88 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java +++ b/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java @@ -52,7 +52,9 @@ public final class XmlStreams { } private static XMLStreamReader createReader(Reader reader) throws XMLStreamException { - return XMLInputFactory.newInstance().createXMLStreamReader(reader); + XMLInputFactory factory = XMLInputFactory.newInstance(); + factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); + return factory.createXMLStreamReader(reader); } diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java new file mode 100644 index 0000000000..3767ac7c06 --- /dev/null +++ b/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java @@ -0,0 +1,114 @@ +package sonia.scm.store; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.TempDirectory; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static sonia.scm.store.CopyOnWrite.withTemporaryFile; + +@ExtendWith(TempDirectory.class) +class CopyOnWriteTest { + + @Test + void shouldCreateNewFile(@TempDirectory.TempDir Path tempDir) { + Path expectedFile = tempDir.resolve("toBeCreated.txt"); + + withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("great success".getBytes()), + expectedFile); + + Assertions.assertThat(expectedFile).hasContent("great success"); + } + + @Test + void shouldOverwriteExistingFile(@TempDirectory.TempDir Path tempDir) throws IOException { + Path expectedFile = tempDir.resolve("toBeOverwritten.txt"); + Files.createFile(expectedFile); + + withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("great success".getBytes()), + expectedFile); + + Assertions.assertThat(expectedFile).hasContent("great success"); + } + + @Test + void shouldFailForDirectory(@TempDirectory.TempDir Path tempDir) { + assertThrows(IllegalArgumentException.class, + () -> withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("should not be written".getBytes()), + tempDir)); + } + + @Test + void shouldFailForMissingDirectory() { + assertThrows( + IllegalArgumentException.class, + () -> withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("should not be written".getBytes()), + Paths.get("someFile"))); + } + + @Test + void shouldKeepBackupIfTemporaryFileCouldNotBeWritten(@TempDirectory.TempDir Path tempDir) throws IOException { + Path unchangedOriginalFile = tempDir.resolve("notToBeDeleted.txt"); + new FileOutputStream(unchangedOriginalFile.toFile()).write("this should be kept".getBytes()); + + assertThrows( + StoreException.class, + () -> withTemporaryFile( + file -> { + throw new IOException("test"); + }, + unchangedOriginalFile)); + + Assertions.assertThat(unchangedOriginalFile).hasContent("this should be kept"); + } + + @Test + void shouldNotWrapRuntimeExceptions(@TempDirectory.TempDir Path tempDir) throws IOException { + Path someFile = tempDir.resolve("something.txt"); + + assertThrows( + NullPointerException.class, + () -> withTemporaryFile( + file -> { + throw new NullPointerException("test"); + }, + someFile)); + } + + @Test + void shouldKeepBackupIfTemporaryFileIsMissing(@TempDirectory.TempDir Path tempDir) throws IOException { + Path backedUpFile = tempDir.resolve("notToBeDeleted.txt"); + new FileOutputStream(backedUpFile.toFile()).write("this should be kept".getBytes()); + + assertThrows( + StoreException.class, + () -> withTemporaryFile( + Files::delete, + backedUpFile)); + + Assertions.assertThat(backedUpFile).hasContent("this should be kept"); + } + + @Test + void shouldDeleteExistingFile(@TempDirectory.TempDir Path tempDir) throws IOException { + Path expectedFile = tempDir.resolve("toBeReplaced.txt"); + new FileOutputStream(expectedFile.toFile()).write("this should be removed".getBytes()); + + withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("overwritten".getBytes()), + expectedFile); + + Assertions.assertThat(Files.list(tempDir)).hasSize(1); + } +} diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 1611633449..e6b2929bd2 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -72,7 +72,13 @@ org.jboss.resteasy - resteasy-jaxrs + resteasy-core + test + + + + org.jboss.resteasy + resteasy-core-spi test diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index 2048d13dea..aa362b8ec6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -70,6 +70,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import static java.util.Optional.empty; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; @@ -209,7 +210,7 @@ public class GitBrowseCommand extends AbstractGitCommand logger.trace("fetch last commit for {} at {}", path, revId.getName()); RevCommit commit = getLatestCommit(repo, revId, path); - Optional lfsPointer = GitUtil.getLfsPointer(repo, path, commit, treeWalk); + Optional lfsPointer = commit == null? empty(): GitUtil.getLfsPointer(repo, path, commit, treeWalk); if (lfsPointer.isPresent()) { BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); diff --git a/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.tsx b/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.tsx index f871ab8f2f..9f36abf302 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.tsx @@ -14,6 +14,12 @@ const Switcher = styled(ButtonAddons)` right: 0; `; +const SmallButton = styled(Button).attrs(props => ({ + className: "is-small" +}))` + height: inherit; +`; + type Props = { repository: Repository; }; @@ -62,9 +68,9 @@ export default class ProtocolInformation extends React.Component { } return ( - + ); }; diff --git a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.tsx b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.tsx index 3ef02306fe..a23fc5040a 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.tsx @@ -1,7 +1,7 @@ import React, { FormEvent } from "react"; import { WithTranslation, withTranslation } from "react-i18next"; import { Branch, Repository, Link } from "@scm-manager/ui-types"; -import { apiClient, BranchSelector, ErrorPage, Loading, Subtitle, SubmitButton } from "@scm-manager/ui-components"; +import { apiClient, BranchSelector, ErrorPage, Loading, Subtitle, Level, SubmitButton } from "@scm-manager/ui-components"; type Props = WithTranslation & { repository: Repository; @@ -143,10 +143,14 @@ class RepositoryConfig extends React.Component { } const submitButton = disabled ? null : ( - + } /> ); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index c25b5c5b60..a561914b42 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -2,14 +2,11 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; @@ -27,6 +24,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.web.GitVndMediaType; +import sonia.scm.web.RestDispatcher; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; @@ -52,10 +50,7 @@ public class GitConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @@ -89,7 +84,7 @@ public class GitConfigResourceTest { when(repositoryHandler.getConfig()).thenReturn(gitConfig); GitRepositoryConfigResource gitRepositoryConfigResource = new GitRepositoryConfigResource(repositoryConfigMapper, repositoryManager, new GitRepositoryConfigStoreProvider(configurationStoreFactory)); GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, of(gitRepositoryConfigResource)); - dispatcher.getRegistry().addSingletonResource(gitConfigResource); + dispatcher.addSingletonResource(gitConfigResource); when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); } @@ -137,10 +132,11 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:read:git]"); + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = get(); - get(); + assertEquals("Subject does not have permission [configuration:read:git]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -152,10 +148,11 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:write:git]"); + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = put(); - put(); + assertEquals("Subject does not have permission [configuration:write:git]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java index d16f056889..cb09c32870 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java @@ -145,7 +145,7 @@ public class HgPackageReader */ private void filterPackage(HgPackages packages) { - List pkgList = new ArrayList(); + List pkgList = new ArrayList<>(); for (HgPackage pkg : packages) { @@ -228,7 +228,7 @@ public class HgPackageReader if (packages == null) { packages = new HgPackages(); - packages.setPackages(new ArrayList()); + packages.setPackages(new ArrayList<>()); } return packages; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java index 1f88bfe665..5a3b809973 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java @@ -2,14 +2,11 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; @@ -18,12 +15,15 @@ import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.web.HgVndMediaType; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; import java.net.URISyntaxException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,10 +37,7 @@ public class HgConfigAutoConfigurationResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @InjectMocks private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper; @@ -57,7 +54,7 @@ public class HgConfigAutoConfigurationResourceTest { new HgConfigAutoConfigurationResource(dtoToConfigMapper, repositoryHandler); when(resourceProvider.get()).thenReturn(resource); - dispatcher.getRegistry().addSingletonResource( + dispatcher.addSingletonResource( new HgConfigResource(null, null, null, null, resourceProvider, null)); } @@ -76,9 +73,10 @@ public class HgConfigAutoConfigurationResourceTest { @Test @SubjectAware(username = "readOnly") public void shouldNotSetDefaultConfigAndInstallHgWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + MockHttpResponse response = put(null); - put(null); + assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -95,9 +93,10 @@ public class HgConfigAutoConfigurationResourceTest { @Test @SubjectAware(username = "readOnly") public void shouldNotUpdateConfigAndInstallHgWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + MockHttpResponse response = put("{\"disabled\":true}"); - put("{\"disabled\":true}"); + assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private MockHttpResponse put(String content) throws URISyntaxException { diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java index bcd9543d28..e77ed917ed 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java @@ -2,19 +2,17 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -35,10 +33,7 @@ public class HgConfigInstallationsResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @@ -57,7 +52,7 @@ public class HgConfigInstallationsResourceTest { HgConfigInstallationsResource resource = new HgConfigInstallationsResource(mapper); when(resourceProvider.get()).thenReturn(resource); - dispatcher.getRegistry().addSingletonResource( + dispatcher.addSingletonResource( new HgConfigResource(null, null, null, null, null, resourceProvider)); @@ -82,9 +77,10 @@ public class HgConfigInstallationsResourceTest { @Test @SubjectAware(username = "writeOnly") public void shouldNotGetHgInstallationsWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + MockHttpResponse response = get("hg"); - get("hg"); + assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -104,9 +100,10 @@ public class HgConfigInstallationsResourceTest { @Test @SubjectAware(username = "writeOnly") public void shouldNotGetPythonInstallationsWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + MockHttpResponse response = get("python"); - get("python"); + assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private MockHttpResponse get(String path) throws URISyntaxException { diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java index 473ddfe4b4..9cd0e4b17e 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java @@ -5,14 +5,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; @@ -23,6 +20,7 @@ import sonia.scm.installer.HgPackageReader; import sonia.scm.net.ahc.AdvancedHttpClient; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -49,10 +47,7 @@ public class HgConfigPackageResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = java.net.URI.create("/"); @@ -113,9 +108,10 @@ public class HgConfigPackageResourceTest { @Test @SubjectAware(username = "writeOnly") public void shouldNotGetPackagesWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + MockHttpResponse response = get(); - get(); + assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -158,9 +154,10 @@ public class HgConfigPackageResourceTest { @Test @SubjectAware(username = "readOnly") public void shouldNotInstallPackageWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + MockHttpResponse response = put("don-t-care"); - put("don-t-care"); + assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private List createPackages() { @@ -191,7 +188,7 @@ public class HgConfigPackageResourceTest { new HgConfigPackageResource(hgPackageReader, advancedHttpClient, repositoryHandler, mapper); when(hgConfigPackageResourceProvider.get()).thenReturn(hgConfigPackageResource); - dispatcher.getRegistry().addSingletonResource( + dispatcher.addSingletonResource( new HgConfigResource(null, null, null, hgConfigPackageResourceProvider, null, null)); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index e0253ad86a..eae03f729d 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -4,14 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; @@ -20,6 +17,7 @@ import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.web.HgVndMediaType; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -43,10 +41,7 @@ public class HgConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @@ -78,7 +73,7 @@ public class HgConfigResourceTest { HgConfigResource gitConfigResource = new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, packagesResource, autoconfigResource, installationsResource); - dispatcher.getRegistry().addSingletonResource(gitConfigResource); + dispatcher.addSingletonResource(gitConfigResource); when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); } @@ -120,10 +115,11 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = get(); - get(); + assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -135,10 +131,11 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = put(); - put(); + assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private MockHttpResponse get() throws URISyntaxException { diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/logging/SVNKitLogger.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/logging/SVNKitLogger.java index e156defaf5..177bc05069 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/logging/SVNKitLogger.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/logging/SVNKitLogger.java @@ -211,15 +211,7 @@ public class SVNKitLogger extends SVNDebugLogAdapter */ private Logger getLogger(SVNLogType type) { - Logger logger = loggerMap.get(type); - - if (logger == null) - { - logger = LoggerFactory.getLogger(parseName(type.getName())); - loggerMap.put(type, logger); - } - - return logger; + return loggerMap.computeIfAbsent(type, t -> LoggerFactory.getLogger(parseName(t.getName()))); } //~--- fields --------------------------------------------------------------- diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java index f7ccf039b2..ee2918160c 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java @@ -4,14 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; @@ -19,6 +16,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.SvnConfig; import sonia.scm.repository.SvnRepositoryHandler; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.SvnVndMediaType; import javax.servlet.http.HttpServletResponse; @@ -42,10 +40,7 @@ public class SvnConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @@ -66,7 +61,7 @@ public class SvnConfigResourceTest { SvnConfig gitConfig = createConfiguration(); when(repositoryHandler.getConfig()).thenReturn(gitConfig); SvnConfigResource gitConfigResource = new SvnConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler); - dispatcher.getRegistry().addSingletonResource(gitConfigResource); + dispatcher.addSingletonResource(gitConfigResource); when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); } @@ -108,10 +103,11 @@ public class SvnConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:read:svn]"); + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = get(); - get(); + assertEquals("Subject does not have permission [configuration:read:svn]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -123,10 +119,11 @@ public class SvnConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:write:svn]"); + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = put(); - put(); + assertEquals("Subject does not have permission [configuration:write:svn]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private MockHttpResponse get() throws URISyntaxException { diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 74f6466821..4abaa49dc1 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -78,6 +78,7 @@ java.awt.headless=true logback.configurationFile=logging.xml + ClassLoaderLeakPreventor.threadWaitMs=100 diff --git a/scm-test/pom.xml b/scm-test/pom.xml index bddbcf2c85..d36b8c55f8 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -42,6 +42,20 @@ ${mockito.version} + + org.jboss.resteasy + resteasy-core-spi + + + org.jboss.resteasy + resteasy-core + + + org.jboss.resteasy + resteasy-jackson2-provider + ${resteasy.version} + + org.slf4j slf4j-simple diff --git a/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java b/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java index 0e4faddb4a..1f606fc908 100644 --- a/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java +++ b/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java @@ -81,15 +81,7 @@ public class MapCacheManager @Override public synchronized MapCache getCache(String name) { - MapCache cache = cacheMap.get(name); - - if (cache == null) - { - cache = new MapCache(); - cacheMap.put(name, cache); - } - - return cache; + return (MapCache) cacheMap.computeIfAbsent(name, k -> new MapCache()); } //~--- fields --------------------------------------------------------------- diff --git a/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java b/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java new file mode 100644 index 0000000000..29d57f204f --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java @@ -0,0 +1,111 @@ +package sonia.scm.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.UnauthorizedException; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.spi.Dispatcher; +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.AlreadyExistsException; +import sonia.scm.BadRequestException; +import sonia.scm.ConcurrentModificationException; +import sonia.scm.NotFoundException; +import sonia.scm.ScmConstraintViolationException; + +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.HashMap; +import java.util.Map; + +public class RestDispatcher { + + private static final Logger LOG = LoggerFactory.getLogger(RestDispatcher.class); + + private final Dispatcher dispatcher; + private final EnhanceableExceptionMapper exceptionMapper; + + public RestDispatcher() { + dispatcher = MockDispatcherFactory.createDispatcher(); + exceptionMapper = new EnhanceableExceptionMapper(); + dispatcher.getProviderFactory().register(exceptionMapper); + dispatcher.getProviderFactory().registerProviderInstance(new JacksonProducer()); + } + + public void addSingletonResource(Object resource) { + dispatcher.getRegistry().addSingletonResource(resource); + } + + public void invoke(HttpRequest in, HttpResponse response) { + dispatcher.invoke(in, response); + } + + public void registerException(Class exceptionClass, Status status) { + exceptionMapper.registerException(exceptionClass, status); + } + + public void putDefaultContextObject(Class clazz, T object) { + dispatcher.getDefaultContextObjects().put(clazz, object); + } + + private static class EnhanceableExceptionMapper implements ExceptionMapper { + + private final Map, Integer> statusCodes = new HashMap<>(); + + public EnhanceableExceptionMapper() { + registerException(NotFoundException.class, Status.NOT_FOUND); + registerException(AlreadyExistsException.class, Status.CONFLICT); + registerException(ConcurrentModificationException.class, Status.CONFLICT); + registerException(UnauthorizedException.class, Status.FORBIDDEN); + registerException(AuthorizationException.class, Status.FORBIDDEN); + registerException(BadRequestException.class, Status.BAD_REQUEST); + registerException(ScmConstraintViolationException.class, Status.BAD_REQUEST); + } + + private void registerException(Class exceptionClass, Status status) { + statusCodes.put(exceptionClass, status.getStatusCode()); + } + + @Override + public Response toResponse(Exception e) { + return Response.status(getStatus(e)).entity(e.getMessage()).build(); + } + + private Integer getStatus(Exception ex) { + return statusCodes + .entrySet() + .stream() + .filter(e -> e.getKey().isAssignableFrom(ex.getClass())) + .map(Map.Entry::getValue) + .findAny() + .orElse(handleUnknownException(ex)); + } + + private Integer handleUnknownException(Exception ex) { + LOG.info("got unknown exception in rest api test", ex); + return 500; + } + } + + @Provider + @Produces("application/*+json") + public static class JacksonProducer implements ContextResolver { + public JacksonProducer() { + this.json + = new ObjectMapper().findAndRegisterModules(); + } + + @Override + public ObjectMapper getContext(Class objectType) { + return json; + } + + private final ObjectMapper json; + } +} diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml index ffc727f10d..50bd414118 100644 --- a/scm-ui/pom.xml +++ b/scm-ui/pom.xml @@ -17,6 +17,8 @@ scm-ui + build + false typescript ui-extensions/src,ui-components/src,ui-webapp/src **/*.test.js,src/tests/** @@ -68,7 +70,7 @@ run - + @@ -118,4 +120,21 @@ + + + + dev + + + + development + + + + + build:dev + + + + diff --git a/scm-ui/prettier-config/package.json b/scm-ui/prettier-config/package.json index 584081645c..097b07ee2b 100644 --- a/scm-ui/prettier-config/package.json +++ b/scm-ui/prettier-config/package.json @@ -7,7 +7,7 @@ "private": false, "main": "index.js", "dependencies": { - "prettier": "^1.18.2" + "prettier": "^1.19.1" }, "publishConfig": { "access": "public" diff --git a/scm-ui/tsconfig/package.json b/scm-ui/tsconfig/package.json index 4ee8613469..0ddbfc5975 100644 --- a/scm-ui/tsconfig/package.json +++ b/scm-ui/tsconfig/package.json @@ -7,7 +7,7 @@ "private": false, "main": "tsconfig.json", "dependencies": { - "typescript": "^3.6.4" + "typescript": "^3.7.2" }, "publishConfig": { "access": "public" diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index e7266aae6f..946416ca96 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -42,7 +42,7 @@ "raf": "^3.4.0", "react-test-renderer": "^16.10.2", "storybook-addon-i18next": "^1.2.1", - "typescript": "^3.6.4" + "typescript": "^3.7.2" }, "dependencies": { "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", diff --git a/scm-ui/ui-components/src/Autocomplete.tsx b/scm-ui/ui-components/src/Autocomplete.tsx index 7618464b57..e7fff461e2 100644 --- a/scm-ui/ui-components/src/Autocomplete.tsx +++ b/scm-ui/ui-components/src/Autocomplete.tsx @@ -1,4 +1,5 @@ import React from "react"; +import classNames from "classnames"; import { Async, AsyncCreatable } from "react-select"; import { SelectValue } from "@scm-manager/ui-types"; import LabelWithHelpIcon from "./forms/LabelWithHelpIcon"; @@ -7,13 +8,14 @@ import { ActionMeta, ValueType } from "react-select/lib/types"; type Props = { loadSuggestions: (p: string) => Promise; valueSelected: (p: SelectValue) => void; - label: string; + label?: string; helpText?: string; value?: SelectValue; placeholder: string; loadingMessage: string; noOptionsMessage: string; creatable?: boolean; + className?: string; }; type State = {}; @@ -53,10 +55,11 @@ class Autocomplete extends React.Component { loadingMessage, noOptionsMessage, loadSuggestions, - creatable + creatable, + className } = this.props; return ( -
    +
    {creatable ? ( diff --git a/scm-ui/ui-components/src/UserGroupAutocomplete.tsx b/scm-ui/ui-components/src/UserGroupAutocomplete.tsx index 5c047b9353..f4e445eba9 100644 --- a/scm-ui/ui-components/src/UserGroupAutocomplete.tsx +++ b/scm-ui/ui-components/src/UserGroupAutocomplete.tsx @@ -1,6 +1,7 @@ import React from "react"; import { SelectValue, AutocompleteObject } from "@scm-manager/ui-types"; import Autocomplete from "./Autocomplete"; +import { apiClient } from "./apiclient"; export type AutocompleteProps = { autocompleteLink?: string; @@ -19,7 +20,8 @@ export default class UserGroupAutocomplete extends React.Component { loadSuggestions = (inputValue: string): Promise => { const url = this.props.autocompleteLink; const link = url + "?q="; - return fetch(link + inputValue) + return apiClient + .get(link + inputValue) .then(response => response.json()) .then((json: AutocompleteObject[]) => { return json.map(element => { diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 0350b44219..ac6a6638fc 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -336,7 +336,7 @@ exports[`Storyshots DateFromNow Default 1`] = ` exports[`Storyshots Forms|Checkbox Default 1`] = `