| {this.createFileIcon(file)} |
{this.createFileName(file)}
- {fileSize} |
+ {fileSize}
|
- {file.description}
+ {file.description}
{binder.hasExtension("repos.sources.tree.row.right") && (
{!file.directory && (
diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.tsx
index dd52604611..dd0183d51d 100644
--- a/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.tsx
+++ b/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.tsx
@@ -18,7 +18,7 @@ class FileButtonAddons extends React.Component {
};
color = (selected: boolean) => {
- return selected ? "link is-selected" : null;
+ return selected ? "link is-selected" : "";
};
render() {
@@ -27,14 +27,14 @@ class FileButtonAddons extends React.Component {
return (
-
-
+
diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx
index c75b0106a9..2ea57a65ba 100644
--- a/scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx
+++ b/scm-ui/ui-webapp/src/repos/sources/containers/SourceExtensions.tsx
@@ -7,24 +7,25 @@ import { fetchSources, getFetchSourcesFailure, getSources, isFetchSourcesPending
import { connect } from "react-redux";
import { Loading, ErrorNotification } from "@scm-manager/ui-components";
import Notification from "@scm-manager/ui-components/src/Notification";
-import {WithTranslation, withTranslation} from "react-i18next";
+import { WithTranslation, withTranslation } from "react-i18next";
-type Props = WithTranslation & RouteComponentProps & {
- repository: Repository;
+type Props = WithTranslation &
+ RouteComponentProps & {
+ repository: Repository;
- // url params
- extension: string;
- revision?: string;
- path?: string;
+ // url params
+ extension: string;
+ revision?: string;
+ path?: string;
- // redux state
- loading: boolean;
- error?: Error | null;
- sources?: File | null;
+ // redux state
+ loading: boolean;
+ error?: Error | null;
+ sources?: File | null;
- // dispatch props
- fetchSources: (repository: Repository, revision: string, path: string) => void;
-};
+ // dispatch props
+ fetchSources: (repository: Repository, revision: string, path: string) => void;
+ };
const extensionPointName = "repos.sources.extensions";
@@ -32,7 +33,7 @@ class SourceExtensions extends React.Component {
componentDidMount() {
const { fetchSources, repository, revision, path } = this.props;
// TODO get typing right
- fetchSources(repository,revision || "", path || "");
+ fetchSources(repository, revision || "", path || "");
}
render() {
diff --git a/scm-ui/ui-webapp/src/users/components/SetUserPassword.tsx b/scm-ui/ui-webapp/src/users/components/SetUserPassword.tsx
index f060c7494d..e5e3861fac 100644
--- a/scm-ui/ui-webapp/src/users/components/SetUserPassword.tsx
+++ b/scm-ui/ui-webapp/src/users/components/SetUserPassword.tsx
@@ -1,7 +1,7 @@
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { User } from "@scm-manager/ui-types";
-import { SubmitButton, Notification, ErrorNotification, PasswordConfirmation } from "@scm-manager/ui-components";
+import { Level, SubmitButton, Notification, ErrorNotification, PasswordConfirmation } from "@scm-manager/ui-components";
import { setPassword } from "./setPassword";
type Props = WithTranslation & {
@@ -98,15 +98,15 @@ class SetUserPassword extends React.Component {
passwordChanged={this.passwordChanged}
key={this.state.passwordChanged ? "changed" : "unchanged"}
/>
-
+ }
+ />
);
}
diff --git a/scm-ui/ui-webapp/src/users/components/UserForm.tsx b/scm-ui/ui-webapp/src/users/components/UserForm.tsx
index db1fc50ea1..1efd2558cd 100644
--- a/scm-ui/ui-webapp/src/users/components/UserForm.tsx
+++ b/scm-ui/ui-webapp/src/users/components/UserForm.tsx
@@ -6,6 +6,7 @@ import {
Checkbox,
InputField,
PasswordConfirmation,
+ Level,
SubmitButton,
validation as validator
} from "@scm-manager/ui-components";
@@ -166,11 +167,7 @@ class UserForm extends React.Component {
/>
-
+ } />
>
);
diff --git a/scm-ui/ui-webapp/src/users/containers/DeleteUser.tsx b/scm-ui/ui-webapp/src/users/containers/DeleteUser.tsx
index 15beba3c49..6feafaaf91 100644
--- a/scm-ui/ui-webapp/src/users/containers/DeleteUser.tsx
+++ b/scm-ui/ui-webapp/src/users/containers/DeleteUser.tsx
@@ -4,7 +4,7 @@ import { withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history";
import { User } from "@scm-manager/ui-types";
-import { Subtitle, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
+import { Level, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { deleteUser, getDeleteUserFailure, isDeleteUserPending } from "../modules/users";
type Props = WithTranslation & {
@@ -64,13 +64,9 @@ class DeleteUser extends React.Component {
return (
<>
-
+
-
+ } />
>
);
}
diff --git a/scm-ui/ui-webapp/src/users/containers/EditUser.tsx b/scm-ui/ui-webapp/src/users/containers/EditUser.tsx
index e2594caa44..81f639535c 100644
--- a/scm-ui/ui-webapp/src/users/containers/EditUser.tsx
+++ b/scm-ui/ui-webapp/src/users/containers/EditUser.tsx
@@ -41,7 +41,6 @@ class EditUser extends React.Component {
this.modifyUser(user)} user={user} loading={loading} />
-
);
diff --git a/scm-ui/ui-webapp/src/users/containers/Users.tsx b/scm-ui/ui-webapp/src/users/containers/Users.tsx
index bb97eab1b4..d634ed5ca7 100644
--- a/scm-ui/ui-webapp/src/users/containers/Users.tsx
+++ b/scm-ui/ui-webapp/src/users/containers/Users.tsx
@@ -49,13 +49,22 @@ class Users extends React.Component {
componentDidUpdate = (prevProps: Props) => {
const { loading, list, page, usersLink, location, fetchUsersByPage } = this.props;
if (list && page && !loading) {
- const statePage: number = list.page + 1;
+ const statePage: number = this.resolveStatePage();
if (page !== statePage || prevProps.location.search !== location.search) {
fetchUsersByPage(usersLink, page, urls.getQueryStringFromLocation(location));
}
}
};
+ resolveStatePage = (props = this.props) => {
+ const { list } = props;
+ if (list.page) {
+ return list.page + 1;
+ }
+ // set page to 1 if undefined, because if users couldn't be fetched it would lead to a fetch-loop otherwise
+ return 1;
+ };
+
render() {
const { users, loading, error, canAddUsers, t } = this.props;
return (
diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml
index 571c3b5370..d186cb106e 100644
--- a/scm-webapp/pom.xml
+++ b/scm-webapp/pom.xml
@@ -124,7 +124,7 @@
org.jboss.resteasy
- resteasy-jaxrs
+ resteasy-core
@@ -154,26 +154,38 @@
org.jboss.resteasy
- resteasy-validator-provider-11
+ resteasy-validator-provider
${resteasy.version}
- org.hibernate
+ org.hibernate.validator
hibernate-validator
- 5.3.6.Final
+ 6.1.0.Final
javax.el
javax.el-api
- 2.2.4
+ 3.0.0
- org.glassfish.web
+ org.glassfish
javax.el
- 2.2.4
+ 3.0.1-b11
+
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.0
+
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+ 2.3.0
@@ -568,7 +580,7 @@
0.10.5
2.53.1
1.0
- 0.8.17
+ 0.9.6-scm1
Tomcat
e1
javascript:S3827
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java
index b9add14529..ab36672f83 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java
@@ -8,7 +8,6 @@ public abstract class RepositoryDtoToRepositoryMapper extends BaseDtoMapper {
@Mapping(target = "creationDate", ignore = true)
@Mapping(target = "id", ignore = true)
- @Mapping(target = "publicReadable", ignore = true)
@Mapping(target = "healthCheckFailures", ignore = true)
public abstract Repository map(RepositoryDto repositoryDto, @Context String id);
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java
index b431997462..758afd7660 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java
@@ -60,7 +60,6 @@ public class SourceRootResource {
if (revision != null && !revision.isEmpty()) {
browseCommand.setRevision(URLDecoder.decode(revision, "UTF-8"));
}
- browseCommand.setDisableCache(true);
BrowserResult browserResult = browseCommand.getBrowserResult();
if (browserResult != null) {
diff --git a/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java b/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java
index 85f760a71f..52b80679d9 100644
--- a/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java
+++ b/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java
@@ -90,8 +90,12 @@ public class LegmanScmEventBus extends ScmEventBus
@Override
public void post(Object event)
{
- logger.debug("post {} to event bus {}", event, name);
- eventBus.post(event);
+ if (eventBus != null) {
+ logger.debug("post {} to event bus {}", event, name);
+ eventBus.post(event);
+ } else {
+ logger.error("failed to post event {}, because event bus is shutdown", event);
+ }
}
/**
@@ -104,9 +108,12 @@ public class LegmanScmEventBus extends ScmEventBus
@Override
public void register(Object object)
{
- logger.trace("register {} to event bus {}", object, name);
- eventBus.register(object);
-
+ if (eventBus != null) {
+ logger.trace("register {} to event bus {}", object, name);
+ eventBus.register(object);
+ } else {
+ logger.error("failed to register {}, because eventbus is shutdown", object);
+ }
}
/**
@@ -118,22 +125,37 @@ public class LegmanScmEventBus extends ScmEventBus
@Override
public void unregister(Object object)
{
- logger.trace("unregister {} from event bus {}", object, name);
-
- try
- {
- eventBus.unregister(object);
+ if (eventBus != null) {
+ logger.trace("unregister {} from event bus {}", object, name);
+
+ try {
+ eventBus.unregister(object);
+ } catch (IllegalArgumentException ex) {
+ logger.trace("object {} was not registered", object);
+ }
+ } else {
+ logger.error("failed to unregister object {}, because event bus is shutdown", object);
}
- catch (IllegalArgumentException ex)
- {
- logger.trace("object {} was not registered", object);
+ }
+
+ @Subscribe(async = false)
+ public void shutdownEventBus(ShutdownEventBusEvent shutdownEventBusEvent) {
+ if (eventBus != null) {
+ logger.info("shutdown event bus executor for {}, because of received ShutdownEventBusEvent", name);
+ eventBus.shutdown();
+ eventBus = null;
+ } else {
+ logger.warn("event bus was already shutdown");
}
}
@Subscribe(async = false)
public void recreateEventBus(RecreateEventBusEvent recreateEventBusEvent) {
- logger.info("shutdown event bus executor for {}", name);
- eventBus.shutdown();
+ if (eventBus != null) {
+ logger.info("shutdown event bus executor for {}, because of received RecreateEventBusEvent", name);
+ eventBus.shutdown();
+ }
+ logger.info("recreate event bus because of received RecreateEventBusEvent");
eventBus = create();
}
diff --git a/scm-webapp/src/main/java/sonia/scm/event/ShutdownEventBusEvent.java b/scm-webapp/src/main/java/sonia/scm/event/ShutdownEventBusEvent.java
new file mode 100644
index 0000000000..5e5ab9ca5c
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/event/ShutdownEventBusEvent.java
@@ -0,0 +1,4 @@
+package sonia.scm.event;
+
+public class ShutdownEventBusEvent {
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java
index ffb7631922..1d642d9c66 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java
@@ -58,13 +58,16 @@ public class BootstrapContextFilter extends GuiceFilter {
private final BootstrapContextListener listener = new BootstrapContextListener();
+ private ClassLoader webAppClassLoader;
+
/** Field description */
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
-
+ // store webapp classloader for delayed restarts
+ webAppClassLoader = Thread.currentThread().getContextClassLoader();
initializeContext();
}
@@ -97,7 +100,7 @@ public class BootstrapContextFilter extends GuiceFilter {
if (filterConfig == null) {
LOG.error("filter config is null, scm-manager is not initialized");
} else {
- RestartStrategy restartStrategy = RestartStrategy.get();
+ RestartStrategy restartStrategy = RestartStrategy.get(webAppClassLoader);
restartStrategy.restart(new GuiceInjectionContext());
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java
index 223e3cf65b..e3fd5528eb 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java
@@ -66,7 +66,7 @@ public class BootstrapContextListener extends GuiceServletContextListener {
private static final Logger LOG = LoggerFactory.getLogger(BootstrapContextListener.class);
- private final ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create();
+ private ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create();
private ServletContext context;
private InjectionLifeCycle injectionLifeCycle;
@@ -88,14 +88,16 @@ public class BootstrapContextListener extends GuiceServletContextListener {
protected Injector getInjector() {
Throwable startupError = SCMContext.getContext().getStartupError();
if (startupError != null) {
+ LOG.error("received unrecoverable error during startup", startupError);
return createStageOneInjector(SingleView.error(startupError));
} else if (Versions.isTooOld()) {
- LOG.error("Existing version is too old and cannot be migrated to new version. Please update to version {} first", Versions.MIN_VERSION);
+ LOG.error("existing version is too old and cannot be migrated to new version. Please update to version {} first", Versions.MIN_VERSION);
return createStageOneInjector(SingleView.view("/templates/too-old.mustache", HttpServletResponse.SC_CONFLICT));
} else {
try {
return createStageTwoInjector();
} catch (Exception ex) {
+ LOG.error("failed to create stage two injector", ex);
return createStageOneInjector(SingleView.error(ex));
}
}
@@ -110,6 +112,8 @@ public class BootstrapContextListener extends GuiceServletContextListener {
injectionLifeCycle.shutdown();
injectionLifeCycle = null;
classLoaderLifeCycle.shutdown();
+
+ super.contextDestroyed(sce);
}
private Injector createStageTwoInjector() {
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java
index 683507c563..2db60580b1 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java
@@ -5,6 +5,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.event.RecreateEventBusEvent;
import sonia.scm.event.ScmEventBus;
+import sonia.scm.event.ShutdownEventBusEvent;
import java.util.concurrent.atomic.AtomicLong;
@@ -13,20 +14,47 @@ import java.util.concurrent.atomic.AtomicLong;
*/
public class InjectionContextRestartStrategy implements RestartStrategy {
+ private static final String DISABLE_RESTART_PROPERTY = "sonia.scm.restart.disable";
+ private static final String WAIT_PROPERTY = "sonia.scm.restart.wait";
+ private static final String DISABLE_GC_PROPERTY = "sonia.scm.restart.disable-gc";
+
private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
private static final Logger LOG = LoggerFactory.getLogger(InjectionContextRestartStrategy.class);
- private long waitInMs = 250L;
+ private boolean restartEnabled = !Boolean.getBoolean(DISABLE_RESTART_PROPERTY);
+ private long waitInMs = Integer.getInteger(WAIT_PROPERTY, 250);
+ private boolean gcEnabled = !Boolean.getBoolean(DISABLE_GC_PROPERTY);
+
+ private final ClassLoader webAppClassLoader;
+
+ InjectionContextRestartStrategy(ClassLoader webAppClassLoader) {
+ this.webAppClassLoader = webAppClassLoader;
+ }
@VisibleForTesting
void setWaitInMs(long waitInMs) {
this.waitInMs = waitInMs;
}
+ @VisibleForTesting
+ void setGcEnabled(boolean gcEnabled) {
+ this.gcEnabled = gcEnabled;
+ }
+
@Override
public void restart(InjectionContext context) {
- LOG.warn("destroy injection context");
- context.destroy();
+ stop(context);
+ if (restartEnabled) {
+ start(context);
+ } else {
+ LOG.warn("restarting context is disabled");
+ }
+ }
+
+ @SuppressWarnings("squid:S1215") // suppress explicit gc call warning
+ private void start(InjectionContext context) {
+ LOG.debug("use WebAppClassLoader as ContextClassLoader, to avoid ClassLoader leaks");
+ Thread.currentThread().setContextClassLoader(webAppClassLoader);
LOG.warn("send recreate eventbus event");
ScmEventBus.getInstance().post(new RecreateEventBusEvent());
@@ -34,6 +62,12 @@ public class InjectionContextRestartStrategy implements RestartStrategy {
// restart context delayed, to avoid timing problems
new Thread(() -> {
try {
+ if (gcEnabled){
+ LOG.info("call gc to clean up memory from old instances");
+ System.gc();
+ }
+
+ LOG.info("wait {}ms before re starting the context", waitInMs);
Thread.sleep(waitInMs);
LOG.warn("reinitialize injection context");
@@ -45,6 +79,15 @@ public class InjectionContextRestartStrategy implements RestartStrategy {
LOG.error("failed to restart", ex);
}
}, "Delayed-Restart-" + INSTANCE_COUNTER.incrementAndGet()).start();
+ }
+ private void stop(InjectionContext context) {
+ LOG.warn("destroy injection context");
+ context.destroy();
+
+ if (!restartEnabled) {
+ // shutdown eventbus, but do this only if restart is disabled
+ ScmEventBus.getInstance().post(new ShutdownEventBusEvent());
+ }
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java
index 769351a850..6c7dd69259 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java
@@ -13,7 +13,6 @@ public interface RestartStrategy {
* Initialize the injection context.
*/
void initialize();
-
/**
* Destroys the injection context.
*/
@@ -31,8 +30,8 @@ public interface RestartStrategy {
*
* @return configured strategy
*/
- static RestartStrategy get() {
- return new InjectionContextRestartStrategy();
+ static RestartStrategy get(ClassLoader webAppClassLoader) {
+ return new InjectionContextRestartStrategy(webAppClassLoader);
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java
index 512a4fc534..07e7d0409b 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java
@@ -61,7 +61,7 @@ public class SetupContextListener implements ServletContextListener {
@Override
public void run() {
- if (isFirstStart()) {
+ if (shouldCreateAdminAccount()) {
createAdminAccount();
}
if (anonymousUserRequiredButNotExists()) {
@@ -73,8 +73,12 @@ public class SetupContextListener implements ServletContextListener {
return scmConfiguration.isAnonymousAccessEnabled() && !userManager.contains(SCMContext.USER_ANONYMOUS);
}
- private boolean isFirstStart() {
- return userManager.getAll().isEmpty();
+ private boolean shouldCreateAdminAccount() {
+ return userManager.getAll().isEmpty() || onlyAnonymousUserExists();
+ }
+
+ private boolean onlyAnonymousUserExists() {
+ return userManager.getAll().size() == 1 && userManager.contains(SCMContext.USER_ANONYMOUS);
}
private void createAdminAccount() {
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java
index 64d9b75d36..a274dbb33c 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java
@@ -5,7 +5,29 @@ package sonia.scm.lifecycle.classloading;
* find it in a heap dump.
*/
class BootstrapClassLoader extends ClassLoader {
+
+ /**
+ * Marker to find a BootstrapClassLoader, which is already shutdown.
+ */
+ private boolean shutdown = false;
+
BootstrapClassLoader(ClassLoader webappClassLoader) {
super(webappClassLoader);
}
+
+ /**
+ * Returns {@code true} if the classloader was shutdown.
+ *
+ * @return {@code true} if the classloader was shutdown
+ */
+ boolean isShutdown() {
+ return shutdown;
+ }
+
+ /**
+ * Mark the class loader as shutdown.
+ */
+ void markAsShutdown() {
+ shutdown = true;
+ }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
index 24d1c239b4..a64ed6fa43 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
@@ -5,7 +5,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
+import se.jiderhamn.classloader.leak.prevention.cleanup.IIOServiceProviderCleanUp;
import se.jiderhamn.classloader.leak.prevention.cleanup.MBeanCleanUp;
+import se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp;
+import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp;
+import se.jiderhamn.classloader.leak.prevention.preinit.AwtToolkitInitiator;
+import se.jiderhamn.classloader.leak.prevention.preinit.Java2dDisposerInitiator;
+import se.jiderhamn.classloader.leak.prevention.preinit.Java2dRenderQueueInitiator;
import se.jiderhamn.classloader.leak.prevention.preinit.SunAwtAppContextInitiator;
import sonia.scm.lifecycle.LifeCycle;
import sonia.scm.plugin.ChildFirstPluginClassLoader;
@@ -16,9 +22,9 @@ import java.io.IOException;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.Deque;
-import java.util.function.UnaryOperator;
import static com.google.common.base.Preconditions.checkState;
+import static se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp.SHUTDOWN_HOOK_WAIT_MS_DEFAULT;
/**
* Creates and shutdown SCM-Manager ClassLoaders.
@@ -27,23 +33,25 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class);
- private final Deque classLoaders = new ArrayDeque<>();
+ private Deque classLoaders = new ArrayDeque<>();
private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
private final ClassLoader webappClassLoader;
- private ClassLoader bootstrapClassLoader;
- private UnaryOperator classLoaderAppendListener = c -> c;
+ private BootstrapClassLoader bootstrapClassLoader;
+
+ private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() {
+ @Override
+ public C apply(C classLoader) {
+ return classLoader;
+ }
+ };
@VisibleForTesting
public static ClassLoaderLifeCycle create() {
- ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory();
- classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter());
- // the SunAwtAppContextInitiator causes a lot of exceptions and we use no awt
- classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class);
- // the MBeanCleanUp causes a Exception and we use no mbeans
- classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class);
- return new ClassLoaderLifeCycle(Thread.currentThread().getContextClassLoader(), classLoaderLeakPreventorFactory);
+ ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = createClassLoaderLeakPreventorFactory(webappClassLoader);
+ return new ClassLoaderLifeCycle(webappClassLoader, classLoaderLeakPreventorFactory);
}
ClassLoaderLifeCycle(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) {
@@ -51,12 +59,64 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
this.webappClassLoader = initAndAppend(webappClassLoader);
}
+ private static ClassLoaderLeakPreventorFactory createClassLoaderLeakPreventorFactory(ClassLoader webappClassLoader) {
+ // Should threads tied to the web app classloader be forced to stop at application shutdown?
+ boolean stopThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopThreads");
+
+ // Should Timer threads tied to the web app classloader be forced to stop at application shutdown?
+ boolean stopTimerThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopTimerThreads");
+
+ // Should shutdown hooks registered from the application be executed at application shutdown?
+ boolean executeShutdownHooks = Boolean.getBoolean("ClassLoaderLeakPreventor.executeShutdownHooks");
+
+ // No of milliseconds to wait for threads to finish execution, before stopping them.
+ int threadWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.threadWaitMs", ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT);
+
+ /*
+ * No of milliseconds to wait for shutdown hooks to finish execution, before stopping them.
+ * If set to -1 there will be no waiting at all, but Thread is allowed to run until finished.
+ */
+ int shutdownHookWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.shutdownHookWaitMs", SHUTDOWN_HOOK_WAIT_MS_DEFAULT);
+
+ LOG.info("Settings for {} (CL: 0x{}):", ClassLoaderLifeCycle.class.getName(), Integer.toHexString(System.identityHashCode(webappClassLoader)) );
+ LOG.info(" stopThreads = {}", stopThreads);
+ LOG.info(" stopTimerThreads = {}", stopTimerThreads);
+ LOG.info(" executeShutdownHooks = {}", executeShutdownHooks);
+ LOG.info(" threadWaitMs = {} ms", threadWaitMs);
+ LOG.info(" shutdownHookWaitMs = {} ms", shutdownHookWaitMs);
+
+ // use webapp classloader as safe base? or system?
+ ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory(webappClassLoader);
+ classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter());
+
+ final ShutdownHookCleanUp shutdownHookCleanUp = classLoaderLeakPreventorFactory.getCleanUp(ShutdownHookCleanUp.class);
+ shutdownHookCleanUp.setExecuteShutdownHooks(executeShutdownHooks);
+ shutdownHookCleanUp.setShutdownHookWaitMs(shutdownHookWaitMs);
+
+ final StopThreadsCleanUp stopThreadsCleanUp = classLoaderLeakPreventorFactory.getCleanUp(StopThreadsCleanUp.class);
+ stopThreadsCleanUp.setStopThreads(stopThreads);
+ stopThreadsCleanUp.setStopTimerThreads(stopTimerThreads);
+ stopThreadsCleanUp.setThreadWaitMs(threadWaitMs);
+
+ // remove awt and imageio cleanup
+ classLoaderLeakPreventorFactory.removePreInitiator(AwtToolkitInitiator.class);
+ classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class);
+ classLoaderLeakPreventorFactory.removeCleanUp(IIOServiceProviderCleanUp.class);
+ classLoaderLeakPreventorFactory.removePreInitiator(Java2dRenderQueueInitiator.class);
+ classLoaderLeakPreventorFactory.removePreInitiator(Java2dDisposerInitiator.class);
+
+ // the MBeanCleanUp causes a Exception and we use no mbeans
+ classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class);
+
+ return classLoaderLeakPreventorFactory;
+ }
+
public void initialize() {
bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader));
}
@VisibleForTesting
- void setClassLoaderAppendListener(UnaryOperator classLoaderAppendListener) {
+ void setClassLoaderAppendListener(ClassLoaderAppendListener classLoaderAppendListener) {
this.classLoaderAppendListener = classLoaderAppendListener;
}
@@ -84,12 +144,17 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
clap.shutdown();
clap = classLoaders.poll();
}
+ // be sure it is realy empty
+ classLoaders.clear();
+ classLoaders = new ArrayDeque<>();
+
+ bootstrapClassLoader.markAsShutdown();
bootstrapClassLoader = null;
}
- private ClassLoader initAndAppend(ClassLoader originalClassLoader) {
+ private T initAndAppend(T originalClassLoader) {
LOG.debug("init classloader {}", originalClassLoader);
- ClassLoader classLoader = classLoaderAppendListener.apply(originalClassLoader);
+ T classLoader = classLoaderAppendListener.apply(originalClassLoader);
ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
preventor.runPreClassLoaderInitiators();
@@ -98,6 +163,10 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
return classLoader;
}
+ interface ClassLoaderAppendListener {
+ C apply(C classLoader);
+ }
+
private class ClassLoaderAndPreventor {
private final ClassLoader classLoader;
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java
index a30cb20c01..8f602db766 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java
@@ -7,6 +7,7 @@ import org.jboss.resteasy.plugins.server.servlet.ListenerBootstrap;
import org.jboss.resteasy.spi.Registry;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.jboss.resteasy.spi.statistics.StatisticsController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -70,10 +71,21 @@ public class ResteasyAllInOneServletDispatcher extends HttpServletDispatcher {
super.destroy();
deployment.stop();
+ // clear ResourceLocatorInvoker leaks
+ StatisticsController statisticsController = ResteasyProviderFactory.getInstance().getStatisticsController();
+ if (statisticsController != null) {
+ statisticsController.reset();
+ }
+
// ensure everything gets cleared, to avoid classloader leaks
ResteasyProviderFactory.clearInstanceIfEqual(ResteasyProviderFactory.getInstance());
- ResteasyProviderFactory.clearContextData();
RuntimeDelegate.setInstance(null);
+
+ removeDeploymentFromServletContext();
+ }
+
+ private void removeDeploymentFromServletContext() {
+ getServletContext().removeAttribute(ResteasyDeployment.class.getName());
}
private ResteasyDeployment getDeploymentFromServletContext() {
diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
index df6c93515b..161abf54c6 100644
--- a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
@@ -38,39 +38,27 @@ import com.google.common.base.Strings;
import com.google.common.collect.Multimap;
import com.google.common.io.Closeables;
import com.google.inject.Inject;
-
import org.apache.shiro.codec.Base64;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import sonia.scm.config.ScmConfiguration;
import sonia.scm.net.Proxies;
import sonia.scm.net.TrustAllHostnameVerifier;
import sonia.scm.net.TrustAllTrustManager;
import sonia.scm.util.HttpUtil;
-//~--- JDK imports ------------------------------------------------------------
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-import java.net.HttpURLConnection;
-import java.net.InetSocketAddress;
-import java.net.ProtocolException;
-import java.net.Proxy;
-import java.net.SocketAddress;
-import java.net.URL;
-
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-
-import java.util.Set;
import javax.inject.Provider;
-
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.*;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Set;
+
+//~--- JDK imports ------------------------------------------------------------
/**
* Default implementation of the {@link AdvancedHttpClient}. The default
@@ -324,11 +312,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
sc.init(null, trustAllCerts, new java.security.SecureRandom());
connection.setSSLSocketFactory(sc.getSocketFactory());
}
- catch (KeyManagementException ex)
- {
- logger.error("could not disable certificate validation", ex);
- }
- catch (NoSuchAlgorithmException ex)
+ catch (KeyManagementException | NoSuchAlgorithmException ex)
{
logger.error("could not disable certificate validation", ex);
}
diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java
index 84dcda3593..71fbb489b1 100644
--- a/scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java
@@ -34,20 +34,17 @@ package sonia.scm.net.ahc;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.ByteSource;
-
import sonia.scm.plugin.Extension;
import sonia.scm.util.IOUtil;
-//~--- JDK imports ------------------------------------------------------------
-
+import javax.ws.rs.core.MediaType;
+import javax.xml.bind.DataBindingException;
+import javax.xml.bind.JAXB;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import javax.ws.rs.core.MediaType;
-
-import javax.xml.bind.DataBindingException;
-import javax.xml.bind.JAXB;
+//~--- JDK imports ------------------------------------------------------------
/**
* {@link ContentTransformer} for xml. The {@link XmlContentTransformer} uses
@@ -96,15 +93,10 @@ public class XmlContentTransformer implements ContentTransformer
stream = content.openBufferedStream();
object = JAXB.unmarshal(stream, type);
}
- catch (IOException ex)
+ catch (IOException | DataBindingException ex)
{
throw new ContentTransformerException("could not unmarshall content", ex);
- }
- catch (DataBindingException ex)
- {
- throw new ContentTransformerException("could not unmarshall content", ex);
- }
- finally
+ } finally
{
IOUtil.close(stream);
}
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
index 07accbcc76..f57d932e1e 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
@@ -187,7 +187,7 @@ public class DefaultPluginManager implements PluginManager {
if (!pendingInstallations.isEmpty()) {
if (restartAfterInstallation) {
- restart("plugin installation");
+ triggerRestart("plugin installation");
} else {
pendingInstallQueue.addAll(pendingInstallations);
updateMayUninstallFlag();
@@ -205,7 +205,7 @@ public class DefaultPluginManager implements PluginManager {
markForUninstall(installed);
if (restartAfterInstallation) {
- restart("plugin installation");
+ triggerRestart("plugin installation");
} else {
updateMayUninstallFlag();
}
@@ -238,12 +238,20 @@ public class DefaultPluginManager implements PluginManager {
public void executePendingAndRestart() {
PluginPermissions.manage().check();
if (!pendingInstallQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) {
- restart("execute pending plugin changes");
+ triggerRestart("execute pending plugin changes");
}
}
- private void restart(String cause) {
- eventBus.post(new RestartEvent(PluginManager.class, cause));
+ @VisibleForTesting
+ void triggerRestart(String cause) {
+ new Thread(() -> {
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ eventBus.post(new RestartEvent(PluginManager.class, cause));
+ }).start();
}
private void cancelPending(List pendingInstallations) {
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/MultiParentClassLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/MultiParentClassLoader.java
index 5881652ec4..ebd8b6e24e 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/MultiParentClassLoader.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/MultiParentClassLoader.java
@@ -74,7 +74,7 @@ public class MultiParentClassLoader extends ClassLoader
public MultiParentClassLoader(Collection extends ClassLoader> parents)
{
super(null);
- this.parents = new CopyOnWriteArrayList(parents);
+ this.parents = new CopyOnWriteArrayList<>(parents);
}
//~--- get methods ----------------------------------------------------------
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java
index 1a18d696d5..d7b11b5ade 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java
@@ -53,7 +53,7 @@ public final class PluginCenterDto implements Serializable {
private String category;
private String author;
private String avatarUrl;
- private String sha256;
+ private String sha256sum;
@XmlElement(name = "conditions")
private Condition conditions;
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java
index 1b84bca147..56a27a73af 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java
@@ -19,7 +19,7 @@ public abstract class PluginCenterDtoMapper {
for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) {
String url = plugin.getLinks().get("download").getHref();
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
- map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256()
+ map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256sum()
);
plugins.add(new AvailablePlugin(descriptor));
}
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/HealthCheckContextListener.java b/scm-webapp/src/main/java/sonia/scm/repository/HealthCheckContextListener.java
index a0918c3e64..146382771e 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/HealthCheckContextListener.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/HealthCheckContextListener.java
@@ -128,15 +128,7 @@ public class HealthCheckContextListener implements ServletContextListener
{
// excute health checks for all repsitories asynchronous
- SecurityUtils.getSubject().execute(new Runnable()
- {
-
- @Override
- public void run()
- {
- healthChecker.checkAll();
- }
- });
+ SecurityUtils.getSubject().execute(healthChecker::checkAll);
}
//~--- fields -------------------------------------------------------------
diff --git a/scm-webapp/src/main/java/sonia/scm/schedule/CronScheduler.java b/scm-webapp/src/main/java/sonia/scm/schedule/CronScheduler.java
index 7899697746..8577768495 100644
--- a/scm-webapp/src/main/java/sonia/scm/schedule/CronScheduler.java
+++ b/scm-webapp/src/main/java/sonia/scm/schedule/CronScheduler.java
@@ -17,15 +17,17 @@ public class CronScheduler implements Scheduler {
private final ScheduledExecutorService executorService;
private final CronTaskFactory taskFactory;
+ private final CronThreadFactory threadFactory;
@Inject
public CronScheduler(CronTaskFactory taskFactory) {
this.taskFactory = taskFactory;
+ this.threadFactory = new CronThreadFactory();
this.executorService = createExecutor();
}
private ScheduledExecutorService createExecutor() {
- return Executors.newScheduledThreadPool(2, new CronThreadFactory());
+ return Executors.newScheduledThreadPool(2, threadFactory);
}
@Override
@@ -52,6 +54,7 @@ public class CronScheduler implements Scheduler {
@Override
public void close() {
LOG.debug("shutdown underlying executor service");
+ threadFactory.close();
executorService.shutdown();
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/schedule/CronThreadFactory.java b/scm-webapp/src/main/java/sonia/scm/schedule/CronThreadFactory.java
index 6519f500fa..bb732b4512 100644
--- a/scm-webapp/src/main/java/sonia/scm/schedule/CronThreadFactory.java
+++ b/scm-webapp/src/main/java/sonia/scm/schedule/CronThreadFactory.java
@@ -1,5 +1,6 @@
package sonia.scm.schedule;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.shiro.util.ThreadContext;
import java.util.concurrent.ExecutionException;
@@ -19,7 +20,10 @@ class CronThreadFactory implements ThreadFactory, AutoCloseable {
private static final AtomicLong FACTORY_COUNTER = new AtomicLong();
- private final ExecutorService executorService = Executors.newSingleThreadExecutor();
+ private final ExecutorService executorService = Executors.newSingleThreadExecutor(
+ new ThreadFactoryBuilder().setNameFormat("CronThreadFactory-%d").build()
+ );
+
private final long factoryId = FACTORY_COUNTER.incrementAndGet();
private final AtomicLong threadCounter = new AtomicLong();
diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java
index fc653efa52..6914f89286 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java
@@ -168,8 +168,7 @@ public class AuthorizationChangedEventProducer {
}
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
- return repository.isPublicReadable() != beforeModification.isPublicReadable()
- || !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
+ return !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
}
private void fireEventForEveryUser() {
diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java b/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java
index be40ab3a6d..f33f4200c4 100644
--- a/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java
+++ b/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java
@@ -90,7 +90,6 @@ public class MigrateVerbsToPermissionRoles implements UpdateStep {
repository.setCreationDate(oldRepository.creationDate);
repository.setHealthCheckFailures(oldRepository.healthCheckFailures);
repository.setLastModified(oldRepository.lastModified);
- repository.setPublicReadable(oldRepository.publicReadable);
return repository;
}
@@ -149,8 +148,6 @@ public class MigrateVerbsToPermissionRoles implements UpdateStep {
private String name;
@XmlElement(name = "permission")
private final Set permissions = new HashSet<>();
- @XmlElement(name = "public")
- private boolean publicReadable = false;
private boolean archived = false;
private String type;
}
diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java
new file mode 100644
index 0000000000..24bcbc363e
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java
@@ -0,0 +1,96 @@
+package sonia.scm.update.repository;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.SCMContext;
+import sonia.scm.SCMContextProvider;
+import sonia.scm.migration.UpdateStep;
+import sonia.scm.plugin.Extension;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryPermission;
+import sonia.scm.repository.xml.XmlRepositoryDAO;
+import sonia.scm.user.User;
+import sonia.scm.user.xml.XmlUserDAO;
+import sonia.scm.version.Version;
+
+import javax.inject.Inject;
+import javax.xml.bind.JAXBException;
+
+import static sonia.scm.version.Version.parse;
+
+@Extension
+public class PublicFlagUpdateStep implements UpdateStep {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PublicFlagUpdateStep.class);
+
+ private static final String V1_REPOSITORY_BACKUP_FILENAME = "repositories.xml.v1.backup";
+
+ private final SCMContextProvider contextProvider;
+ private final XmlUserDAO userDAO;
+ private final XmlRepositoryDAO repositoryDAO;
+
+ @Inject
+ public PublicFlagUpdateStep(SCMContextProvider contextProvider, XmlUserDAO userDAO, XmlRepositoryDAO repositoryDAO) {
+ this.contextProvider = contextProvider;
+ this.userDAO = userDAO;
+ this.repositoryDAO = repositoryDAO;
+ }
+
+ @Override
+ public void doUpdate() throws JAXBException {
+ LOG.info("Migrating public flags of repositories as RepositoryRolePermission 'READ' for user '_anonymous'");
+ V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_BACKUP_FILENAME).ifPresent(
+ v1RepositoryDatabase -> {
+ createNewAnonymousUserIfNotExists();
+ deleteOldAnonymousUserIfAvailable();
+ addRepositoryReadPermissionForAnonymousUser(v1RepositoryDatabase);
+ }
+ );
+ }
+
+ @Override
+ public Version getTargetVersion() {
+ return parse("2.0.3");
+ }
+
+ @Override
+ public String getAffectedDataType() {
+ return "sonia.scm.repository.xml";
+ }
+
+ private void addRepositoryReadPermissionForAnonymousUser(V1RepositoryHelper.V1RepositoryDatabase v1RepositoryDatabase) {
+ User v2AnonymousUser = userDAO.get(SCMContext.USER_ANONYMOUS);
+ v1RepositoryDatabase.repositoryList.repositories
+ .stream()
+ .filter(V1Repository::isPublic)
+ .forEach(v1Repository -> {
+ Repository v2Repository = repositoryDAO.get(v1Repository.getId());
+ LOG.info(String.format("Add RepositoryRole 'READ' to _anonymous user for repository: %s - %s/%s", v2Repository.getId(), v2Repository.getNamespace(), v2Repository.getName()));
+ v2Repository.addPermission(new RepositoryPermission(v2AnonymousUser.getId(), "READ", false));
+ repositoryDAO.modify(v2Repository);
+ });
+ }
+
+ private void createNewAnonymousUserIfNotExists() {
+ if (!userExists(SCMContext.USER_ANONYMOUS)) {
+ LOG.info("Create new _anonymous user");
+ userDAO.add(SCMContext.ANONYMOUS);
+ }
+ }
+
+ private void deleteOldAnonymousUserIfAvailable() {
+ String oldAnonymous = "anonymous";
+ if (userExists(oldAnonymous)) {
+ User anonymousUser = userDAO.get(oldAnonymous);
+ LOG.info("Delete obsolete anonymous user");
+ userDAO.delete(anonymousUser);
+ }
+ }
+
+ private boolean userExists(String username) {
+ return userDAO
+ .getAll()
+ .stream()
+ .anyMatch(user -> user.getName().equals(username));
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java
index 4ce823bd33..8b389e467b 100644
--- a/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java
+++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java
@@ -4,6 +4,7 @@ import sonia.scm.update.V1Properties;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@@ -16,6 +17,7 @@ public class V1Repository {
private String description;
private String id;
private String name;
+ @XmlElement(name="public")
private boolean isPublic;
private boolean archived;
private String type;
diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java
new file mode 100644
index 0000000000..f2a60c5529
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java
@@ -0,0 +1,57 @@
+package sonia.scm.update.repository;
+
+import sonia.scm.SCMContextProvider;
+import sonia.scm.store.StoreConstants;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Optional;
+
+import static java.util.Optional.empty;
+import static java.util.Optional.of;
+
+class V1RepositoryHelper {
+
+ static Optional resolveV1File(SCMContextProvider contextProvider, String filename) {
+ File v1XmlFile = contextProvider.resolve(Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(filename)).toFile();
+ if (v1XmlFile.exists()) {
+ return Optional.of(v1XmlFile);
+ }
+ return Optional.empty();
+ }
+
+ static Optional readV1Database(SCMContextProvider contextProvider, String filename) throws JAXBException {
+ JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
+ Optional file = resolveV1File(contextProvider, filename);
+ if (file.isPresent()) {
+ Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(file.get());
+ if (unmarshal instanceof V1RepositoryDatabase) {
+ return of((V1RepositoryDatabase) unmarshal);
+ } else {
+ return empty();
+ }
+ }
+ return empty();
+ }
+
+ static class RepositoryList {
+ @XmlElement(name = "repository")
+ List repositories;
+ }
+
+ @XmlRootElement(name = "repository-db")
+ @XmlAccessorType(XmlAccessType.FIELD)
+ static class V1RepositoryDatabase {
+ long creationTime;
+ Long lastModified;
+ @XmlElement(name = "repositories")
+ RepositoryList repositoryList;
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java
index a2f7656498..dd1284492b 100644
--- a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java
@@ -17,26 +17,18 @@ import sonia.scm.update.V1Properties;
import sonia.scm.version.Version;
import javax.inject.Inject;
-import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.emptyList;
-import static java.util.Optional.empty;
-import static java.util.Optional.of;
import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER;
+import static sonia.scm.update.repository.V1RepositoryHelper.resolveV1File;
import static sonia.scm.version.Version.parse;
/**
@@ -59,6 +51,8 @@ import static sonia.scm.version.Version.parse;
@Extension
public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
+ private final String V1_REPOSITORY_FILENAME = "repositories" + StoreConstants.FILE_EXTENSION;
+
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
private final SCMContextProvider contextProvider;
@@ -97,12 +91,11 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
@Override
public void doUpdate() throws JAXBException {
- if (!resolveV1File().exists()) {
+ if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).isPresent()) {
LOG.info("no v1 repositories database file found");
return;
}
- JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
- readV1Database(jaxbContext).ifPresent(
+ V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_FILENAME).ifPresent(
v1Database -> {
v1Database.repositoryList.repositories.forEach(this::readMigrationEntry);
v1Database.repositoryList.repositories.forEach(this::update);
@@ -112,13 +105,12 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
}
public List getRepositoriesWithoutMigrationStrategies() {
- if (!resolveV1File().exists()) {
+ if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).isPresent()) {
LOG.info("no v1 repositories database file found");
return emptyList();
}
try {
- JAXBContext jaxbContext = JAXBContext.newInstance(XmlRepositoryV1UpdateStep.V1RepositoryDatabase.class);
- return readV1Database(jaxbContext)
+ return V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_FILENAME)
.map(v1Database -> v1Database.repositoryList.repositories.stream())
.orElse(Stream.empty())
.filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent())
@@ -196,33 +188,4 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
return new RepositoryPermission(v1Permission.getName(), v1Permission.getType(), v1Permission.isGroupPermission());
}
- private Optional readV1Database(JAXBContext jaxbContext) throws JAXBException {
- Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File());
- if (unmarshal instanceof V1RepositoryDatabase) {
- return of((V1RepositoryDatabase) unmarshal);
- } else {
- return empty();
- }
- }
-
- private File resolveV1File() {
- return contextProvider
- .resolve(
- Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve("repositories" + StoreConstants.FILE_EXTENSION)
- ).toFile();
- }
-
- private static class RepositoryList {
- @XmlElement(name = "repository")
- private List repositories;
- }
-
- @XmlRootElement(name = "repository-db")
- @XmlAccessorType(XmlAccessType.FIELD)
- private static class V1RepositoryDatabase {
- private long creationTime;
- private Long lastModified;
- @XmlElement(name = "repositories")
- private RepositoryList repositoryList;
- }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java
index 8da159f3c0..fa8507c917 100644
--- a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java
+++ b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java
@@ -52,7 +52,7 @@ import javax.servlet.http.HttpServletResponse;
*
* @author Sebastian Sdorra
*/
-public class DefaultCGIExecutorFactory implements CGIExecutorFactory
+public class DefaultCGIExecutorFactory implements CGIExecutorFactory, AutoCloseable
{
/**
@@ -92,6 +92,11 @@ public class DefaultCGIExecutorFactory implements CGIExecutorFactory
//~--- fields ---------------------------------------------------------------
+ @Override
+ public void close() {
+ executor.shutdown();
+ }
+
/** Field description */
private final ExecutorService executor;
}
diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json
index 0be1c1f803..83b463b8b6 100644
--- a/scm-webapp/src/main/resources/locales/en/plugins.json
+++ b/scm-webapp/src/main/resources/locales/en/plugins.json
@@ -198,9 +198,9 @@
}
},
"namespaceStrategies": {
- "UsernameNamespaceStrategy": "Username",
- "CustomNamespaceStrategy": "Custom",
- "CurrentYearNamespaceStrategy": "Current year",
+ "UsernameNamespaceStrategy": "Username",
+ "CustomNamespaceStrategy": "Custom",
+ "CurrentYearNamespaceStrategy": "Current year",
"RepositoryTypeNamespaceStrategy": "Repository type"
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java
new file mode 100644
index 0000000000..cd56d966ef
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java
@@ -0,0 +1,49 @@
+package sonia.scm.api.rest;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectThreadState;
+import org.apache.shiro.util.ThreadContext;
+import org.apache.shiro.util.ThreadState;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class AuthorizationExceptionMapperTest {
+
+ private final Subject subject = mock(Subject.class);
+ private final ThreadState subjectThreadState = new SubjectThreadState(subject);
+
+ @BeforeEach
+ public void init() {
+ subjectThreadState.bind();
+ ThreadContext.bind(subject);
+ }
+
+ @AfterEach
+ public void unbindSubject() {
+ ThreadContext.unbindSubject();
+ }
+
+ @Test
+ void shouldMapNormalUserToForbidden() {
+ when(subject.getPrincipal()).thenReturn("someone");
+
+ assertThat(
+ new AuthorizationExceptionMapper().toResponse(new AuthorizationException()).getStatus()
+ ).isEqualTo(403);
+ }
+
+ @Test
+ void shouldMapAnonymousUserToUnauthorized() {
+ when(subject.getPrincipal()).thenReturn("_anonymous");
+
+ assertThat(
+ new AuthorizationExceptionMapper().toResponse(new AuthorizationException()).getStatus()
+ ).isEqualTo(401);
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java
index 177f975971..1c1035b53a 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java
@@ -2,11 +2,8 @@ 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.jboss.resteasy.spi.ResteasyProviderFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -19,18 +16,17 @@ import sonia.scm.security.AccessTokenBuilder;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.AccessTokenCookieIssuer;
import sonia.scm.security.DefaultAccessTokenCookieIssuer;
+import sonia.scm.web.RestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.io.UnsupportedEncodingException;
-import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.Optional;
import static java.net.URI.create;
-import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
@@ -47,7 +43,7 @@ public class AuthenticationResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
- private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
+ private RestDispatcher dispatcher = new RestDispatcher();
@Mock
private AccessTokenBuilderFactory accessTokenBuilderFactory;
@@ -116,7 +112,7 @@ public class AuthenticationResourceTest {
@Before
public void prepareEnvironment() {
authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer);
- dispatcher.getRegistry().addSingletonResource(authenticationResource);
+ dispatcher.addSingletonResource(authenticationResource);
AccessToken accessToken = mock(AccessToken.class);
when(accessToken.getExpiration()).thenReturn(new Date(Long.MAX_VALUE));
@@ -125,10 +121,9 @@ public class AuthenticationResourceTest {
when(accessTokenBuilderFactory.create()).thenReturn(accessTokenBuilder);
HttpServletRequest servletRequest = mock(HttpServletRequest.class);
- ResteasyProviderFactory.getContextDataMap().put(HttpServletRequest.class, servletRequest);
-
+ dispatcher.putDefaultContextObject(HttpServletRequest.class, servletRequest);
HttpServletResponse servletResponse = mock(HttpServletResponse.class);
- ResteasyProviderFactory.getContextDataMap().put(HttpServletResponse.class, servletResponse);
+ dispatcher.putDefaultContextObject(HttpServletResponse.class, servletResponse);
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java
index ac89d02bfb..3f2473dda0 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java
@@ -5,7 +5,6 @@ import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import org.apache.shiro.util.ThreadContext;
import org.assertj.core.util.Lists;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.After;
@@ -23,6 +22,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.user.DefaultUserDisplayManager;
import sonia.scm.user.User;
import sonia.scm.user.xml.XmlUserDAO;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import sonia.scm.xml.XmlDatabase;
@@ -39,7 +39,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@SubjectAware(configuration = "classpath:sonia/scm/shiro-002.ini")
@RunWith(MockitoJUnitRunner.Silent.class)
@@ -50,7 +49,8 @@ public class AutoCompleteResourceTest {
public static final String URL = "/" + AutoCompleteResource.PATH;
private final Integer defaultLimit = DisplayManager.DEFAULT_LIMIT;
- private Dispatcher dispatcher;
+
+ private RestDispatcher dispatcher = new RestDispatcher();
private XmlUserDAO userDao;
private XmlGroupDAO groupDao;
@@ -74,7 +74,7 @@ public class AutoCompleteResourceTest {
DefaultUserDisplayManager userManager = new DefaultUserDisplayManager(this.userDao);
DefaultGroupDisplayManager groupManager = new DefaultGroupDisplayManager(groupDao);
AutoCompleteResource autoCompleteResource = new AutoCompleteResource(mapper, userManager, groupManager);
- dispatcher = createDispatcher(autoCompleteResource);
+ dispatcher.addSingletonResource(autoCompleteResource);
}
@After
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java
index 32eadf7af0..cfda20f285 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java
@@ -1,14 +1,11 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
-import org.apache.shiro.ShiroException;
+import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
-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.jboss.resteasy.spi.UnhandledException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -24,6 +21,7 @@ import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PluginCondition;
import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginManager;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.inject.Provider;
@@ -34,7 +32,7 @@ import java.util.Collections;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
@@ -46,7 +44,7 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class AvailablePluginResourceTest {
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
@Mock
Provider availablePluginResourceProvider;
@@ -71,10 +69,9 @@ class AvailablePluginResourceTest {
@BeforeEach
void prepareEnvironment() {
- dispatcher = MockDispatcherFactory.createDispatcher();
pluginRootResource = new PluginRootResource(null, availablePluginResourceProvider, null);
when(availablePluginResourceProvider.get()).thenReturn(availablePluginResource);
- dispatcher.getRegistry().addSingletonResource(pluginRootResource);
+ dispatcher.addSingletonResource(pluginRootResource);
}
@Nested
@@ -195,20 +192,23 @@ class AvailablePluginResourceTest {
@BeforeEach
void bindSubject() {
ThreadContext.bind(subject);
- doThrow(new ShiroException()).when(subject).checkPermission(any(String.class));
+ doThrow(new UnauthorizedException()).when(subject).checkPermission(any(String.class));
}
@AfterEach
public void unbindSubject() {
ThreadContext.unbindSubject();
}
+
@Test
void shouldNotGetAvailablePluginsIfMissingPermission() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available");
request.accept(VndMediaType.PLUGIN_COLLECTION);
MockHttpResponse response = new MockHttpResponse();
- assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ dispatcher.invoke(request, response);
+
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
verify(subject).checkPermission(any(String.class));
}
@@ -218,7 +218,9 @@ class AvailablePluginResourceTest {
request.accept(VndMediaType.PLUGIN);
MockHttpResponse response = new MockHttpResponse();
- assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ dispatcher.invoke(request, response);
+
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
verify(subject).checkPermission(any(String.class));
}
@@ -228,7 +230,9 @@ class AvailablePluginResourceTest {
request.accept(VndMediaType.PLUGIN);
MockHttpResponse response = new MockHttpResponse();
- assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ dispatcher.invoke(request, response);
+
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
verify(subject).checkPermission(any(String.class));
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java
index 2c83d5e3e1..72bf9e96d6 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java
@@ -6,9 +6,8 @@ import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
+import org.assertj.core.api.Assertions;
import org.assertj.core.util.Lists;
-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.After;
@@ -30,8 +29,10 @@ import sonia.scm.repository.api.BranchesCommandBuilder;
import sonia.scm.repository.api.LogCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
+import javax.ws.rs.core.MediaType;
import java.net.URI;
import java.time.Instant;
import java.util.Date;
@@ -54,7 +55,8 @@ public class BranchRootResourceTest extends RepositoryTestBase {
public static final String BRANCH_PATH = "space/repo/branches/master";
public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH;
public static final String REVISION = "revision";
- private Dispatcher dispatcher;
+
+ private RestDispatcher dispatcher = new RestDispatcher();
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@@ -101,7 +103,7 @@ public class BranchRootResourceTest extends RepositoryTestBase {
BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks);
branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper, resourceLinks);
super.branchRootResource = Providers.of(branchRootResource);
- dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
@@ -129,7 +131,8 @@ public class BranchRootResourceTest extends RepositoryTestBase {
dispatcher.invoke(request, response);
assertEquals(404, response.getStatus());
- assertEquals("application/vnd.scmm-error+json;v=2", response.getOutputHeaders().getFirst("Content-Type"));
+ MediaType contentType = (MediaType) response.getOutputHeaders().getFirst("Content-Type");
+ Assertions.assertThat(response.getContentAsString()).contains("branch", "master", "space/repo");
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java
index 952c8504f6..1e2b4a0979 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java
@@ -8,7 +8,6 @@ import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.assertj.core.util.Lists;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.After;
@@ -26,6 +25,7 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.api.LogCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import java.net.URI;
@@ -48,7 +48,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
public static final String CHANGESET_PATH = "space/repo/changesets/";
public static final String CHANGESET_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + CHANGESET_PATH;
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@@ -79,7 +79,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper);
super.changesetRootResource = Providers.of(changesetRootResource);
- dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java
index 15c23dbd3c..951f3dc24c 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java
@@ -4,18 +4,16 @@ import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.io.Resources;
import org.apache.shiro.util.ThreadContext;
-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.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.NamespaceStrategyValidator;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
@@ -41,10 +39,7 @@ public class ConfigResourceTest {
@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("/");
@SuppressWarnings("unused") // Is injected
@@ -71,7 +66,7 @@ public class ConfigResourceTest {
ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator);
- dispatcher.getRegistry().addSingletonResource(configResource);
+ dispatcher.addSingletonResource(configResource);
}
@Test
@@ -88,13 +83,14 @@ public class ConfigResourceTest {
@Test
@SubjectAware(username = "writeOnly")
- public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException {
+ public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException {
MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
MockHttpResponse response = new MockHttpResponse();
- thrown.expectMessage("Subject does not have permission [configuration:read:global]");
-
dispatcher.invoke(request, response);
+
+ assertEquals("Subject does not have permission [configuration:read:global]", response.getContentAsString());
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
}
@Test
@@ -120,9 +116,10 @@ public class ConfigResourceTest {
MockHttpRequest request = post("sonia/scm/api/v2/config-test-update.json");
MockHttpResponse response = new MockHttpResponse();
- thrown.expectMessage("Subject does not have permission [configuration:write:global]");
-
dispatcher.invoke(request, response);
+
+ assertEquals("Subject does not have permission [configuration:write:global]", response.getContentAsString());
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java
index 33d4d7e9e1..f01a20f6c4 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java
@@ -7,8 +7,6 @@ import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
-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.After;
@@ -18,16 +16,17 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.NotFoundException;
-import sonia.scm.api.rest.AuthorizationExceptionMapper;
-import sonia.scm.api.v2.NotFoundExceptionMapper;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
+import sonia.scm.util.CRLFInjectionException;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
+import javax.ws.rs.core.Response;
import java.net.URISyntaxException;
import java.util.Arrays;
@@ -46,7 +45,8 @@ public class DiffResourceTest extends RepositoryTestBase {
public static final String DIFF_PATH = "space/repo/diff/";
public static final String DIFF_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + DIFF_PATH;
- private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
+
+ private RestDispatcher dispatcher = new RestDispatcher();
@Mock
private RepositoryServiceFactory serviceFactory;
@@ -68,14 +68,12 @@ public class DiffResourceTest extends RepositoryTestBase {
public void prepareEnvironment() {
diffRootResource = new DiffRootResource(serviceFactory);
super.diffRootResource = Providers.of(diffRootResource);
- dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl();
- dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper));
- dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
- dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class);
+ dispatcher.registerException(CRLFInjectionException.class, Response.Status.BAD_REQUEST);
when(service.getDiffCommand()).thenReturn(diffCommandBuilder);
subjectThreadState.bind();
ThreadContext.bind(subject);
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java
deleted file mode 100644
index fe205e88a1..0000000000
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package sonia.scm.api.v2.resources;
-
-import org.jboss.resteasy.core.Dispatcher;
-import org.jboss.resteasy.mock.MockDispatcherFactory;
-import sonia.scm.api.rest.AlreadyExistsExceptionMapper;
-import sonia.scm.api.rest.AuthorizationExceptionMapper;
-import sonia.scm.api.rest.BadRequestExceptionMapper;
-import sonia.scm.api.rest.ConcurrentModificationExceptionMapper;
-import sonia.scm.api.v2.NotFoundExceptionMapper;
-
-public class DispatcherMock {
- public static Dispatcher createDispatcher(Object resource) {
- Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
- dispatcher.getRegistry().addSingletonResource(resource);
- ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl();
- dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper));
- dispatcher.getProviderFactory().register(new AlreadyExistsExceptionMapper(mapper));
- dispatcher.getProviderFactory().register(new ConcurrentModificationExceptionMapper(mapper));
- dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
- dispatcher.getProviderFactory().register(new InternalRepositoryExceptionMapper(mapper));
- dispatcher.getProviderFactory().register(new BadRequestExceptionMapper(mapper));
- return dispatcher;
- }
-}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java
index a8b3c15158..5803fbec9f 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java
@@ -7,7 +7,6 @@ import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.assertj.core.util.Lists;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.After;
@@ -28,6 +27,7 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.api.LogCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import java.net.URI;
@@ -70,7 +70,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
private FileHistoryRootResource fileHistoryRootResource;
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
@@ -80,7 +80,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper);
super.fileHistoryRootResource = Providers.of(fileHistoryRootResource);
- dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java
index 09236c0e19..5f2abb85f0 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java
@@ -4,7 +4,6 @@ import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.io.Resources;
import com.google.inject.util.Providers;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
@@ -15,12 +14,11 @@ import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.PageResult;
-import sonia.scm.api.rest.JSONContextResolver;
-import sonia.scm.api.rest.ObjectMapperProvider;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
@@ -43,7 +41,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@SubjectAware(
username = "trillian",
@@ -55,7 +52,7 @@ public class GroupRootResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/"));
@@ -91,8 +88,7 @@ public class GroupRootResourceTest {
GroupResource groupResource = new GroupResource(groupManager, groupToDtoMapper, dtoToGroupMapper, groupPermissionResource);
GroupRootResource groupRootResource = new GroupRootResource(Providers.of(groupCollectionResource), Providers.of(groupResource));
- dispatcher = createDispatcher(groupRootResource);
- dispatcher.getProviderFactory().registerProviderInstance(new JSONContextResolver(new ObjectMapperProvider().get()));
+ dispatcher.addSingletonResource(groupRootResource);
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java
index 0c1f4235b9..27a9f0d6a5 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java
@@ -8,7 +8,6 @@ import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.assertj.core.util.Lists;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.After;
@@ -28,8 +27,11 @@ import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.LogCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
+import sonia.scm.util.CRLFInjectionException;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
+import javax.ws.rs.core.Response;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
@@ -53,7 +55,7 @@ public class IncomingRootResourceTest extends RepositoryTestBase {
public static final String INCOMING_CHANGESETS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + INCOMING_PATH;
public static final String INCOMING_DIFF_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + INCOMING_PATH;
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@@ -88,13 +90,13 @@ public class IncomingRootResourceTest extends RepositoryTestBase {
incomingChangesetCollectionToDtoMapper = new IncomingChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
incomingRootResource = new IncomingRootResource(serviceFactory, incomingChangesetCollectionToDtoMapper);
super.incomingRootResource = Providers.of(incomingRootResource);
- dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
when(repositoryService.getLogCommand()).thenReturn(logCommandBuilder);
when(repositoryService.getDiffCommand()).thenReturn(diffCommandBuilder);
- dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class);
+ dispatcher.registerException(CRLFInjectionException.class, Response.Status.BAD_REQUEST);
subjectThreadState.bind();
ThreadContext.bind(subject);
when(subject.isPermitted(any(String.class))).thenReturn(true);
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java
index e2a23f0d52..d1d406c6af 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java
@@ -1,13 +1,11 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
+import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
-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.jboss.resteasy.spi.UnhandledException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -17,9 +15,9 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.InstalledPlugin;
-import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginManager;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.inject.Provider;
@@ -31,15 +29,17 @@ import java.util.Optional;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static sonia.scm.plugin.PluginTestHelper.createInstalled;
@ExtendWith(MockitoExtension.class)
class InstalledPluginResourceTest {
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
@Mock
Provider installedPluginResourceProvider;
@@ -65,10 +65,9 @@ class InstalledPluginResourceTest {
@BeforeEach
void prepareEnvironment() {
- dispatcher = MockDispatcherFactory.createDispatcher();
pluginRootResource = new PluginRootResource(installedPluginResourceProvider, null, null);
when(installedPluginResourceProvider.get()).thenReturn(installedPluginResource);
- dispatcher.getRegistry().addSingletonResource(pluginRootResource);
+ dispatcher.addSingletonResource(pluginRootResource);
}
@Nested
@@ -77,7 +76,6 @@ class InstalledPluginResourceTest {
@BeforeEach
void bindSubject() {
ThreadContext.bind(subject);
- when(subject.isPermitted(any(String.class))).thenReturn(true);
}
@AfterEach
@@ -129,7 +127,13 @@ class InstalledPluginResourceTest {
class WithoutAuthorization {
@BeforeEach
- void unbindSubject() {
+ void bindSubject() {
+ ThreadContext.bind(subject);
+ doThrow(new UnauthorizedException()).when(subject).checkPermission(any(String.class));
+ }
+
+ @AfterEach
+ public void unbindSubject() {
ThreadContext.unbindSubject();
}
@@ -139,7 +143,9 @@ class InstalledPluginResourceTest {
request.accept(VndMediaType.PLUGIN_COLLECTION);
MockHttpResponse response = new MockHttpResponse();
- assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ dispatcher.invoke(request, response);
+
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
}
@Test
@@ -148,7 +154,9 @@ class InstalledPluginResourceTest {
request.accept(VndMediaType.PLUGIN);
MockHttpResponse response = new MockHttpResponse();
- assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ dispatcher.invoke(request, response);
+
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java
index 7a3d1b4304..433a17b49a 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java
@@ -7,7 +7,6 @@ import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
@@ -21,6 +20,7 @@ import sonia.scm.group.GroupCollector;
import sonia.scm.user.InvalidPasswordException;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
@@ -37,7 +37,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@SubjectAware(
username = "trillian",
@@ -49,9 +48,10 @@ public class MeResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/"));
+
@Mock
private ScmPathInfo uriInfo;
@Mock
@@ -85,7 +85,7 @@ public class MeResourceTest {
MeResource meResource = new MeResource(meDtoFactory, userManager, passwordService);
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/"));
when(scmPathInfoStore.get()).thenReturn(uriInfo);
- dispatcher = createDispatcher(meResource);
+ dispatcher.addSingletonResource(meResource);
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java
index fc4598081d..41217f9fb0 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java
@@ -7,7 +7,6 @@ import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.assertj.core.util.Lists;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.After;
@@ -24,6 +23,7 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.api.ModificationsCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import java.net.URI;
@@ -45,7 +45,7 @@ public class ModificationsResourceTest extends RepositoryTestBase {
public static final String MODIFICATIONS_PATH = "space/repo/modifications/";
public static final String MODIFICATIONS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + MODIFICATIONS_PATH;
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@@ -73,7 +73,7 @@ public class ModificationsResourceTest extends RepositoryTestBase {
public void prepareEnvironment() {
modificationsRootResource = new ModificationsRootResource(serviceFactory, modificationsToDtoMapper);
super.modificationsRootResource = Providers.of(modificationsRootResource);
- dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java
index da6e1b65d7..17e16729b8 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java
@@ -4,8 +4,6 @@ import com.google.inject.util.Providers;
import org.apache.shiro.ShiroException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
-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.jupiter.api.AfterEach;
@@ -22,10 +20,10 @@ import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginManager;
+import sonia.scm.web.RestDispatcher;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;
-import javax.ws.rs.ext.ExceptionMapper;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
@@ -42,7 +40,7 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class PendingPluginResourceTest {
- Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
+ private RestDispatcher dispatcher = new RestDispatcher();
ResourceLinks resourceLinks = ResourceLinksMock.createMock(create("/"));
@@ -61,10 +59,9 @@ class PendingPluginResourceTest {
@BeforeEach
void prepareEnvironment() {
- dispatcher = MockDispatcherFactory.createDispatcher();
- dispatcher.getProviderFactory().register(new PermissionExceptionMapper());
+ dispatcher.registerException(ShiroException.class, Response.Status.UNAUTHORIZED);
PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource));
- dispatcher.getRegistry().addSingletonResource(pluginRootResource);
+ dispatcher.addSingletonResource(pluginRootResource);
}
@BeforeEach
@@ -207,14 +204,6 @@ class PendingPluginResourceTest {
}
}
- static class PermissionExceptionMapper implements ExceptionMapper {
-
- @Override
- public Response toResponse(ShiroException exception) {
- return Response.status(401).entity(exception.getMessage()).build();
- }
- }
-
private AvailablePlugin createAvailablePlugin(String name) {
PluginInformation pluginInformation = new PluginInformation();
pluginInformation.setName(name);
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java
index f2b835a0a1..294f55f5d2 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java
@@ -13,7 +13,6 @@ import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.assertj.core.util.Lists;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.spi.HttpRequest;
@@ -30,6 +29,7 @@ import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryPermission;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.ws.rs.HttpMethod;
@@ -58,7 +58,6 @@ import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
@Slf4j
@@ -105,7 +104,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
.content(PERMISSION_TEST_PAYLOAD)
.path(PATH_OF_ONE_PERMISSION);
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
@Mock
private RepositoryManager repositoryManager;
@@ -133,7 +132,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
repositoryPermissionCollectionToDtoMapper = new RepositoryPermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks);
repositoryPermissionRootResource = new RepositoryPermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager);
super.permissionRootResource = Providers.of(repositoryPermissionRootResource);
- dispatcher = createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
subjectThreadState.bind();
ThreadContext.bind(subject);
}
@@ -180,19 +179,6 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
requestPUTPermission.expectedResponseStatus(403));
}
- @TestFactory
- @DisplayName("test endpoints on missing permissions and is _anonymous")
- Stream missedPermissionAnonymousUnauthorizedTestFactory() {
- when(subject.getPrincipal()).thenReturn("_anonymous");
- doThrow(AuthorizationException.class).when(repositoryManager).get(any(NamespaceAndName.class));
- return createDynamicTestsToAssertResponses(
- requestGETPermission.expectedResponseStatus(401),
- requestPOSTPermission.expectedResponseStatus(401),
- requestGETAllPermissions.expectedResponseStatus(401),
- requestDELETEPermission.expectedResponseStatus(401),
- requestPUTPermission.expectedResponseStatus(401));
- }
-
@Test
public void userWithPermissionWritePermissionShouldGetAllPermissionsWithCreateAndUpdateLinks() throws URISyntaxException {
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRoleRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRoleRootResourceTest.java
index af968efd1f..f288b12006 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRoleRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRoleRootResourceTest.java
@@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.inject.util.Providers;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
@@ -16,10 +15,9 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.PageResult;
-import sonia.scm.api.rest.JSONContextResolver;
-import sonia.scm.api.rest.ObjectMapperProvider;
import sonia.scm.repository.RepositoryRole;
import sonia.scm.repository.RepositoryRoleManager;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
@@ -36,7 +34,6 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@SubjectAware(
username = "trillian",
@@ -66,7 +63,7 @@ public class RepositoryRoleRootResourceTest {
private RepositoryRoleCollectionToDtoMapper collectionToDtoMapper;
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
@Captor
private ArgumentCaptor modifyCaptor;
@@ -87,8 +84,7 @@ public class RepositoryRoleRootResourceTest {
when(repositoryRoleManager.create(createCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
doNothing().when(repositoryRoleManager).delete(deleteCaptor.capture());
- dispatcher = createDispatcher(rootResource);
- dispatcher.getProviderFactory().registerProviderInstance(new JSONContextResolver(new ObjectMapperProvider().get()));
+ dispatcher.addSingletonResource(rootResource);
when(repositoryRoleManager.get(CUSTOM_ROLE)).thenReturn(CUSTOM_REPOSITORY_ROLE);
when(repositoryRoleManager.get(SYSTEM_ROLE)).thenReturn(SYSTEM_REPOSITORY_ROLE);
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
index af1e91344a..f4c49be693 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
@@ -6,7 +6,6 @@ import com.google.common.io.Resources;
import com.google.inject.util.Providers;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
@@ -23,6 +22,7 @@ import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.user.User;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
@@ -47,12 +47,10 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@SubjectAware(
username = "trillian",
@@ -63,7 +61,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
private static final String REALM = "AdminRealm";
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
@Rule
public ShiroRule shiro = new ShiroRule();
@@ -98,7 +96,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
super.manager = repositoryManager;
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
super.repositoryCollectionResource = Providers.of(new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks));
- dispatcher = createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(scmPathInfoStore.get()).thenReturn(uriInfo);
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java
index 2476785d70..fcbff438a2 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java
@@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.util.Providers;
-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;
@@ -15,6 +13,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryType;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import java.net.URI;
@@ -32,7 +31,7 @@ import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.Silent.class)
public class RepositoryTypeRootResourceTest {
- private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
+ private RestDispatcher dispatcher = new RestDispatcher();
@Mock
private RepositoryManager repositoryManager;
@@ -56,7 +55,7 @@ public class RepositoryTypeRootResourceTest {
RepositoryTypeCollectionResource collectionResource = new RepositoryTypeCollectionResource(repositoryManager, collectionMapper);
RepositoryTypeResource resource = new RepositoryTypeResource(repositoryManager, mapper);
RepositoryTypeRootResource rootResource = new RepositoryTypeRootResource(Providers.of(collectionResource), Providers.of(resource));
- dispatcher.getRegistry().addSingletonResource(rootResource);
+ dispatcher.addSingletonResource(rootResource);
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java
index 1a45d3b233..37c7659b1c 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java
@@ -1,7 +1,6 @@
package sonia.scm.api.v2.resources;
import com.google.inject.util.Providers;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
@@ -18,6 +17,7 @@ import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.api.BrowseCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
+import sonia.scm.web.RestDispatcher;
import java.io.IOException;
import java.net.URI;
@@ -25,13 +25,12 @@ import java.net.URISyntaxException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@RunWith(MockitoJUnitRunner.Silent.class)
public class SourceRootResourceTest extends RepositoryTestBase {
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@@ -58,7 +57,7 @@ public class SourceRootResourceTest extends RepositoryTestBase {
SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToFileObjectDtoMapper);
super.sourceRootResource = Providers.of(sourceRootResource);
- dispatcher = createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java
index 803f2b106c..8a61dc8e25 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java
@@ -7,7 +7,6 @@ import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.assertj.core.util.Lists;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.After;
@@ -17,8 +16,6 @@ import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
-import sonia.scm.api.rest.AuthorizationExceptionMapper;
-import sonia.scm.api.v2.NotFoundExceptionMapper;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.Tag;
@@ -26,6 +23,7 @@ import sonia.scm.repository.Tags;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.TagsCommandBuilder;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import java.net.URI;
@@ -36,7 +34,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@Slf4j
@RunWith(MockitoJUnitRunner.Silent.class)
@@ -44,7 +41,8 @@ public class TagRootResourceTest extends RepositoryTestBase {
public static final String TAG_PATH = "space/repo/tags/";
public static final String TAG_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + TAG_PATH;
- private Dispatcher dispatcher ;
+
+ private RestDispatcher dispatcher = new RestDispatcher();
private final URI baseUri = URI.create("/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@@ -74,12 +72,10 @@ public class TagRootResourceTest extends RepositoryTestBase {
tagCollectionToDtoMapper = new TagCollectionToDtoMapper(resourceLinks, tagToTagDtoMapper);
tagRootResource = new TagRootResource(serviceFactory, tagCollectionToDtoMapper, tagToTagDtoMapper);
super.tagRootResource = Providers.of(tagRootResource);
- dispatcher = createDispatcher(getRepositoryRootResource());
+ dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
- dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class);
- dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
when(repositoryService.getTagsCommand()).thenReturn(tagsCommandBuilder);
subjectThreadState.bind();
ThreadContext.bind(subject);
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java
index 4987dec644..23e914da35 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java
@@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.util.Providers;
-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;
@@ -12,7 +10,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
-import sonia.scm.plugin.*;
+import sonia.scm.plugin.InstalledPlugin;
+import sonia.scm.plugin.InstalledPluginDescriptor;
+import sonia.scm.plugin.PluginInformation;
+import sonia.scm.plugin.PluginLoader;
+import sonia.scm.plugin.PluginResources;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletRequest;
@@ -24,14 +27,17 @@ import java.util.HashSet;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.hamcrest.Matchers.equalToIgnoringCase;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.Silent.class)
public class UIRootResourceTest {
- private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
+ private RestDispatcher dispatcher = new RestDispatcher();
@Mock
private PluginLoader pluginLoader;
@@ -50,7 +56,7 @@ public class UIRootResourceTest {
UIPluginResource pluginResource = new UIPluginResource(pluginLoader, collectionMapper, mapper);
UIRootResource rootResource = new UIRootResource(Providers.of(pluginResource));
- dispatcher.getRegistry().addSingletonResource(rootResource);
+ dispatcher.addSingletonResource(rootResource);
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java
index 06d9b89120..d49d1a82b9 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java
@@ -5,7 +5,6 @@ import com.github.sdorra.shiro.SubjectAware;
import com.google.common.io.Resources;
import com.google.inject.util.Providers;
import org.apache.shiro.authc.credential.PasswordService;
-import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
@@ -23,6 +22,7 @@ import sonia.scm.security.PermissionDescriptor;
import sonia.scm.user.ChangePasswordNotAllowedException;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
+import sonia.scm.web.RestDispatcher;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
@@ -45,7 +45,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
-import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@SubjectAware(
username = "trillian",
@@ -57,7 +56,7 @@ public class UserRootResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
- private Dispatcher dispatcher;
+ private RestDispatcher dispatcher = new RestDispatcher();
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/"));
@@ -99,7 +98,7 @@ public class UserRootResourceTest {
UserRootResource userRootResource = new UserRootResource(Providers.of(userCollectionResource),
Providers.of(userResource));
- dispatcher = createDispatcher(userRootResource);
+ dispatcher.addSingletonResource(userRootResource);
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/cache/CacheTestBase.java b/scm-webapp/src/test/java/sonia/scm/cache/CacheTestBase.java
index 54cf7991c5..f0396741aa 100644
--- a/scm-webapp/src/test/java/sonia/scm/cache/CacheTestBase.java
+++ b/scm-webapp/src/test/java/sonia/scm/cache/CacheTestBase.java
@@ -35,18 +35,14 @@ package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
-import com.google.common.base.Predicate;
-
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
-
import sonia.scm.util.IOUtil;
-import static org.hamcrest.Matchers.*;
-
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
-import org.junit.Assume;
/**
*
@@ -166,14 +162,7 @@ public abstract class CacheTestBase
cache.put("a-1", "test123");
cache.put("a-2", "test123");
- Iterable previous = cache.removeAll(new Predicate()
- {
- @Override
- public boolean apply(String item)
- {
- return item.startsWith("test");
- }
- });
+ Iterable previous = cache.removeAll(item -> item != null && item.startsWith("test"));
assertThat(previous, containsInAnyOrder("test123", "test456"));
assertNull(cache.get("test-1"));
@@ -188,8 +177,8 @@ public abstract class CacheTestBase
// skip test if implementation does not support stats
Assume.assumeTrue( stats != null );
assertEquals("test", stats.getName());
- assertEquals(0l, stats.getHitCount());
- assertEquals(0l, stats.getMissCount());
+ assertEquals(0L, stats.getHitCount());
+ assertEquals(0L, stats.getMissCount());
cache.put("test-1", "test123");
cache.put("test-2", "test456");
cache.get("test-1");
@@ -197,11 +186,11 @@ public abstract class CacheTestBase
cache.get("test-1");
cache.get("test-3");
// check that stats have not changed
- assertEquals(0l, stats.getHitCount());
- assertEquals(0l, stats.getMissCount());
+ assertEquals(0L, stats.getHitCount());
+ assertEquals(0L, stats.getMissCount());
stats = cache.getStatistics();
- assertEquals(3l, stats.getHitCount());
- assertEquals(1l, stats.getMissCount());
+ assertEquals(3L, stats.getHitCount());
+ assertEquals(1L, stats.getMissCount());
assertEquals(0.75d, stats.getHitRate(), 0.0d);
assertEquals(0.25d, stats.getMissRate(), 0.0d);
}
diff --git a/scm-webapp/src/test/java/sonia/scm/cache/GuavaConfigurationReaderTest.java b/scm-webapp/src/test/java/sonia/scm/cache/GuavaConfigurationReaderTest.java
index 7f7be94203..ff8b59cfc3 100644
--- a/scm-webapp/src/test/java/sonia/scm/cache/GuavaConfigurationReaderTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/cache/GuavaConfigurationReaderTest.java
@@ -63,7 +63,7 @@ public class GuavaConfigurationReaderTest
GuavaCacheConfiguration cfg =
readConfiguration("gcache.001.xml").getDefaultCache();
- assertCacheValues(cfg, 200l, 1200l, 2400l);
+ assertCacheValues(cfg, 200L, 1200L, 2400L);
}
/**
@@ -82,10 +82,10 @@ public class GuavaConfigurationReaderTest
//J+
// cache sonia.test.cache.001 override by cache.004.xml
- assertCacheValues(getCache(gcm, "sonia.test.cache.001"), 6l, 2l, 8l);
- assertCacheValues(getCache(gcm, "sonia.test.cache.002"), 1000l, 120l, 60l);
- assertCacheValues(getCache(gcm, "sonia.test.cache.003"), 3000l, 120l,
- 2400l);
+ assertCacheValues(getCache(gcm, "sonia.test.cache.001"), 6L, 2L, 8L);
+ assertCacheValues(getCache(gcm, "sonia.test.cache.002"), 1000L, 120L, 60L);
+ assertCacheValues(getCache(gcm, "sonia.test.cache.003"), 3000L, 120L,
+ 2400L);
}
/**
@@ -100,8 +100,8 @@ public class GuavaConfigurationReaderTest
// check default
- assertCacheValues(getCache(gcm, "sonia.test.cache.001"), 1000l, 60l, 30l);
- assertCacheValues(getCache(gcm, "sonia.test.cache.002"), 1000l, 120l, 60l);
+ assertCacheValues(getCache(gcm, "sonia.test.cache.001"), 1000L, 60L, 30L);
+ assertCacheValues(getCache(gcm, "sonia.test.cache.002"), 1000L, 120L, 60L);
}
/**
@@ -115,10 +115,10 @@ public class GuavaConfigurationReaderTest
Iterators.forArray("gcache.002.xml",
"gcache.003.xml"));
- assertCacheValues(getCache(gcm, "sonia.test.cache.001"), 1000l, 60l, 30l);
- assertCacheValues(getCache(gcm, "sonia.test.cache.002"), 1000l, 120l, 60l);
- assertCacheValues(getCache(gcm, "sonia.test.cache.003"), 3000l, 120l,
- 2400l);
+ assertCacheValues(getCache(gcm, "sonia.test.cache.001"), 1000L, 60L, 30L);
+ assertCacheValues(getCache(gcm, "sonia.test.cache.002"), 1000L, 120L, 60L);
+ assertCacheValues(getCache(gcm, "sonia.test.cache.003"), 3000L, 120L,
+ 2400L);
}
/**
@@ -131,7 +131,7 @@ public class GuavaConfigurationReaderTest
GuavaCacheConfiguration cfg =
readConfiguration("gcache.001.xml").getCaches().get(0);
- assertCacheValues(cfg, 1000l, 60l, 30l);
+ assertCacheValues(cfg, 1000L, 60L, 30L);
}
/**
@@ -144,7 +144,7 @@ public class GuavaConfigurationReaderTest
GuavaCacheConfiguration cfg =
readConfiguration("gcache.002.xml").getCaches().get(0);
- assertCacheValues(cfg, 1000l, 120l, 60l);
+ assertCacheValues(cfg, 1000L, 120L, 60L);
}
/**
diff --git a/scm-webapp/src/test/java/sonia/scm/it/RepositorySimplePermissionITCase.java b/scm-webapp/src/test/java/sonia/scm/it/RepositorySimplePermissionITCase.java
index 7900f11096..5a0ca8fc38 100644
--- a/scm-webapp/src/test/java/sonia/scm/it/RepositorySimplePermissionITCase.java
+++ b/scm-webapp/src/test/java/sonia/scm/it/RepositorySimplePermissionITCase.java
@@ -94,7 +94,6 @@ public class RepositorySimplePermissionITCase
repository.setName("test-repo");
repository.setType("git");
-// repository.setPublicReadable(false);
ScmClient client = createAdminClient();
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java
index 355dca4a16..ddd691e5da 100644
--- a/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java
@@ -18,11 +18,13 @@ class InjectionContextRestartStrategyTest {
@Mock
private RestartStrategy.InjectionContext context;
- private InjectionContextRestartStrategy strategy = new InjectionContextRestartStrategy();
+ private InjectionContextRestartStrategy strategy = new InjectionContextRestartStrategy(Thread.currentThread().getContextClassLoader());
@BeforeEach
void setWaitToZero() {
strategy.setWaitInMs(0L);
+ // disable gc during tests
+ strategy.setGcEnabled(false);
}
@Test
@@ -47,7 +49,6 @@ class InjectionContextRestartStrategyTest {
@Test
void shouldRegisterContextAfterRestart() throws InterruptedException {
TestingInjectionContext ctx = new TestingInjectionContext();
-
strategy.restart(ctx);
Thread.sleep(50L);
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java
index 27ddd42cc1..efacbd3594 100644
--- a/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java
@@ -70,7 +70,19 @@ class SetupContextListenerTest {
}
@Test
- void shouldCreateAdminAccountAndAssignPermissions() {
+ void shouldCreateAdminAccountIfNoUserExistsAndAssignPermissions() {
+ when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
+
+ setupContextListener.contextInitialized(null);
+
+ verifyAdminCreated();
+ verifyAdminPermissionsAssigned();
+ }
+
+ @Test
+ void shouldCreateAdminAccountIfOnlyAnonymousUserExistsAndAssignPermissions() {
+ when(userManager.getAll()).thenReturn(Lists.newArrayList(SCMContext.ANONYMOUS));
+ when(userManager.contains(SCMContext.USER_ANONYMOUS)).thenReturn(true);
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
setupContextListener.contextInitialized(null);
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
index 0ab98039d1..a8f37777d7 100644
--- a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
@@ -70,7 +70,12 @@ class ClassLoaderLifeCycleTest {
URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader);
- lifeCycle.setClassLoaderAppendListener(c -> spy(c));
+ lifeCycle.setClassLoaderAppendListener(new ClassLoaderLifeCycle.ClassLoaderAppendListener() {
+ @Override
+ public C apply(C classLoader) {
+ return spy(classLoader);
+ }
+ });
lifeCycle.initialize();
ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java
index f817476848..f6aa1d7301 100644
--- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java
@@ -12,13 +12,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.ArgumentCaptor;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.NotFoundException;
import sonia.scm.ScmConstraintViolationException;
-import sonia.scm.event.ScmEventBus;
-import sonia.scm.lifecycle.RestartEvent;
import java.io.IOException;
import java.nio.file.Files;
@@ -48,9 +45,6 @@ import static sonia.scm.plugin.PluginTestHelper.createInstalled;
@ExtendWith(TempDirectory.class)
class DefaultPluginManagerTest {
- @Mock
- private ScmEventBus eventBus;
-
@Mock
private PluginLoader loader;
@@ -60,12 +54,13 @@ class DefaultPluginManagerTest {
@Mock
private PluginInstaller installer;
- @InjectMocks
private DefaultPluginManager manager;
@Mock
private Subject subject;
+ private boolean restartTriggered = false;
+
@BeforeEach
void mockInstaller() {
lenient().when(installer.install(any())).then(ic -> {
@@ -74,6 +69,16 @@ class DefaultPluginManagerTest {
});
}
+ @BeforeEach
+ void createPluginManagerToTestWithCapturedRestart() {
+ manager = new DefaultPluginManager(null, loader, center, installer) { // event bus is only used in restart and this is replaced here
+ @Override
+ void triggerRestart(String cause) {
+ restartTriggered = true;
+ }
+ };
+ }
+
@Nested
class WithAdminPermissions {
@@ -180,7 +185,7 @@ class DefaultPluginManagerTest {
manager.install("scm-git-plugin", false);
verify(installer).install(git);
- verify(eventBus, never()).post(any());
+ assertThat(restartTriggered).isFalse();
}
@Test
@@ -258,7 +263,7 @@ class DefaultPluginManagerTest {
manager.install("scm-git-plugin", true);
verify(installer).install(git);
- verify(eventBus).post(any(RestartEvent.class));
+ assertThat(restartTriggered).isTrue();
}
@Test
@@ -267,7 +272,7 @@ class DefaultPluginManagerTest {
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(gitInstalled));
manager.install("scm-git-plugin", true);
- verify(eventBus, never()).post(any());
+ assertThat(restartTriggered).isFalse();
}
@Test
@@ -289,14 +294,14 @@ class DefaultPluginManagerTest {
manager.install("scm-review-plugin", false);
manager.executePendingAndRestart();
- verify(eventBus).post(any(RestartEvent.class));
+ assertThat(restartTriggered).isTrue();
}
@Test
void shouldNotSendRestartEventWithoutPendingPlugins() {
manager.executePendingAndRestart();
- verify(eventBus, never()).post(any());
+ assertThat(restartTriggered).isFalse();
}
@Test
@@ -447,7 +452,7 @@ class DefaultPluginManagerTest {
manager.executePendingAndRestart();
- verify(eventBus).post(any(RestartEvent.class));
+ assertThat(restartTriggered).isTrue();
}
@Test
diff --git a/scm-webapp/src/test/java/sonia/scm/security/ConfigurableLoginAttemptHandlerTest.java b/scm-webapp/src/test/java/sonia/scm/security/ConfigurableLoginAttemptHandlerTest.java
index 714438b8fe..01c4766396 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/ConfigurableLoginAttemptHandlerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/ConfigurableLoginAttemptHandlerTest.java
@@ -74,7 +74,7 @@ public class ConfigurableLoginAttemptHandlerTest {
handler.onUnsuccessfulAuthentication(token, new SimpleAuthenticationInfo());
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, new SimpleAuthenticationInfo());
- Thread.sleep(TimeUnit.MILLISECONDS.toMillis(1200l));
+ Thread.sleep(TimeUnit.MILLISECONDS.toMillis(1200L));
handler.beforeAuthentication(token);
}
@@ -111,4 +111,4 @@ public class ConfigurableLoginAttemptHandlerTest {
return new ConfigurableLoginAttemptHandler(configuration);
}
-}
\ No newline at end of file
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultKeyGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultKeyGeneratorTest.java
index edec43098c..57b038d5f1 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/DefaultKeyGeneratorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultKeyGeneratorTest.java
@@ -35,22 +35,15 @@ package sonia.scm.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Sets;
-
import org.junit.Test;
+import java.util.Set;
+import java.util.concurrent.*;
+
import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
/**
*
* @author Sebastian Sdorra
@@ -96,28 +89,22 @@ public class DefaultKeyGeneratorTest
for (int i = 0; i < 10; i++)
{
- Future> future = executor.submit(new Callable>()
- {
+ Future> future = executor.submit(() -> {
+ Set keys = Sets.newHashSet();
- @Override
- public Set call()
+ for (int i1 = 0; i1 < 1000; i1++)
{
- Set keys = Sets.newHashSet();
+ String key = generator.createKey();
- for (int i = 0; i < 1000; i++)
+ if (keys.contains(key))
{
- String key = generator.createKey();
-
- if (keys.contains(key))
- {
- fail("dublicate key");
- }
-
- keys.add(key);
+ fail("dublicate key");
}
- return keys;
+ keys.add(key);
}
+
+ return keys;
});
futureSet.add(future);
diff --git a/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTest.java b/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTest.java
index 186b809367..bfac40b4df 100644
--- a/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTest.java
@@ -89,15 +89,8 @@ public class MustacheTemplateTest extends TemplateTestBase
@Override
protected void prepareEnv(Map env)
{
- env.put("test", new Function()
- {
-
- @Override
- public String apply(String input)
- {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
+ env.put("test", (Function) input -> {
+ throw new UnsupportedOperationException("Not supported yet.");
});
}
diff --git a/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java
new file mode 100644
index 0000000000..cbd8312642
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java
@@ -0,0 +1,118 @@
+package sonia.scm.update.repository;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junitpioneer.jupiter.TempDirectory;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import sonia.scm.SCMContext;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryPermission;
+import sonia.scm.repository.RepositoryRolePermissions;
+import sonia.scm.repository.RepositoryTestData;
+import sonia.scm.repository.xml.XmlRepositoryDAO;
+import sonia.scm.update.UpdateStepTestUtil;
+import sonia.scm.user.User;
+import sonia.scm.user.xml.XmlUserDAO;
+
+import javax.xml.bind.JAXBException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junitpioneer.jupiter.TempDirectory.TempDir;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+@ExtendWith(TempDirectory.class)
+class PublicFlagUpdateStepTest {
+
+ @Mock
+ XmlUserDAO userDAO;
+ @Mock
+ XmlRepositoryDAO repositoryDAO;
+ @Captor
+ ArgumentCaptor repositoryCaptor;
+
+ private UpdateStepTestUtil testUtil;
+ private PublicFlagUpdateStep updateStep;
+ private Repository REPOSITORY = RepositoryTestData.createHeartOfGold();
+
+ @BeforeEach
+ void mockScmHome(@TempDir Path tempDir) throws IOException {
+ testUtil = new UpdateStepTestUtil(tempDir);
+ updateStep = new PublicFlagUpdateStep(testUtil.getContextProvider(), userDAO, repositoryDAO);
+
+ //prepare backup xml
+ V1RepositoryFileSystem.createV1Home(tempDir);
+ Files.move(tempDir.resolve("config").resolve("repositories.xml"), tempDir.resolve("config").resolve("repositories.xml.v1.backup"));
+ when(repositoryDAO.get((String) any())).thenReturn(REPOSITORY);
+ }
+
+ @Test
+ void shouldDeleteOldAnonymousUserIfExists() throws JAXBException {
+ User anonymous = new User("anonymous");
+ when(userDAO.getAll()).thenReturn(Collections.singleton(anonymous));
+ doReturn(anonymous).when(userDAO).get("anonymous");
+ doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
+
+ updateStep.doUpdate();
+
+ verify(userDAO).delete(anonymous);
+ }
+
+ @Test
+ void shouldNotTryToDeleteOldAnonymousUserIfNotExists() throws JAXBException {
+ when(userDAO.getAll()).thenReturn(Collections.emptyList());
+ doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
+
+ updateStep.doUpdate();
+
+ verify(userDAO, never()).delete(any());
+ }
+
+ @Test
+ void shouldCreateNewAnonymousUserIfNotExists() throws JAXBException {
+ doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
+ when(userDAO.getAll()).thenReturn(Collections.singleton(new User("trillian")));
+
+ updateStep.doUpdate();
+
+ verify(userDAO).add(SCMContext.ANONYMOUS);
+ }
+
+ @Test
+ void shouldNotCreateNewAnonymousUserIfAlreadyExists() throws JAXBException {
+ doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
+ when(userDAO.getAll()).thenReturn(Collections.singleton(new User("_anonymous")));
+
+ updateStep.doUpdate();
+
+ verify(userDAO, never()).add(SCMContext.ANONYMOUS);
+ }
+
+ @Test
+ void shouldMigratePublicFlagToAnonymousRepositoryPermission() throws JAXBException {
+ when(userDAO.getAll()).thenReturn(Collections.emptyList());
+ when(userDAO.get("_anonymous")).thenReturn(SCMContext.ANONYMOUS);
+
+ updateStep.doUpdate();
+
+ verify(repositoryDAO, times(2)).modify(repositoryCaptor.capture());
+
+ RepositoryPermission migratedRepositoryPermission = repositoryCaptor.getValue().getPermissions().iterator().next();
+ assertThat(migratedRepositoryPermission.getName()).isEqualTo(SCMContext.USER_ANONYMOUS);
+ assertThat(migratedRepositoryPermission.getRole()).isEqualTo("READ");
+ assertThat(migratedRepositoryPermission.isGroupPermission()).isFalse();
+ }
+}
diff --git a/scm-webapp/src/test/resources/sonia/scm/update/repository/scm-home.v1.zip b/scm-webapp/src/test/resources/sonia/scm/update/repository/scm-home.v1.zip
index 5d936db5e2..4b21785f20 100644
Binary files a/scm-webapp/src/test/resources/sonia/scm/update/repository/scm-home.v1.zip and b/scm-webapp/src/test/resources/sonia/scm/update/repository/scm-home.v1.zip differ
diff --git a/yarn.lock b/yarn.lock
index d382f742c5..35cfe2e580 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11692,6 +11692,11 @@ prettier@^1.16.4, prettier@^1.18.2:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
+prettier@^1.19.1:
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
+ integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
+
pretty-error@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
@@ -14430,11 +14435,16 @@ typescript-compiler@^1.4.1-2:
resolved "https://registry.yarnpkg.com/typescript-compiler/-/typescript-compiler-1.4.1-2.tgz#ba4f7db22d91534a1929d90009dce161eb72fd3f"
integrity sha1-uk99si2RU0oZKdkACdzhYety/T8=
-typescript@^3.4, typescript@^3.6.4:
+typescript@^3.4:
version "3.6.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
+typescript@^3.7.2:
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
+ integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
+
ua-parser-js@^0.7.18:
version "0.7.20"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098"
|