diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..7bd36e0adc
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line=lf
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+charset = utf-8
+
+# 4 space indentation
+[*.py]
+indent_style = space
+indent_size = 4
diff --git a/.hgignore b/.hgignore
index 14e7f7a85d..a49329d7a5 100644
--- a/.hgignore
+++ b/.hgignore
@@ -1,6 +1,7 @@
# netbeans temp & private files
/?target/
nbactions.*\.xml
+/?nbproject/
nb-configuration\.xml
# MacOS X Files
\.DS_Store$
@@ -25,3 +26,5 @@ Desktop DF$
# idea files
\.iml
\.idea$
+# jrebel
+rebel.xml
diff --git a/pom.xml b/pom.xml
index f40b5f943b..5c22ea0433 100644
--- a/pom.xml
+++ b/pom.xml
@@ -262,7 +262,7 @@
http://download.oracle.com/javase/6/docs/api/
http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/
http://jersey.java.net/nonav/apidocs/${jersey.version}/jersey/
- http://google-guice.googlecode.com/svn/tags/3.0/javadoc/
+ https://google.github.io/guice/api-docs/${guice.version}/javadoc
http://www.slf4j.org/api/
http://shiro.apache.org/static/current/apidocs/
@@ -274,7 +274,7 @@
maven-source-plugin2.2.1
-
+
org.apache.maven.pluginsmaven-clean-plugin
@@ -419,16 +419,6 @@
-
- JDK7
-
- 1.7
-
-
- -jdk7
-
-
-
APIviz
@@ -449,8 +439,8 @@
org.jboss.apiviz.APIvizorg.jboss.apiviz
- apiviz${jdk.classifier}
- 1.3.1.GA
+ apiviz
+ 1.3.2.GA
-sourceclasspath ${project.build.outputDirectory}
@@ -484,40 +474,41 @@
- 1.10.8
+ 1.10.191.3
- 4.11
+ 4.12
- 1.7.7
- 1.1.2
+ 1.7.21
+ 1.1.73.0.14.0
- 1.18.2
+ 1.19.11.2.09.2.10.v20150310
+ 9.2.10.v201503101.0.0-SNAPSHOT1.4.0-RC2
- 3.4.1.201406201815-r
+ 4.4.0.201606070830-r-scm11.8.5-scm316.0.1
+ 2.2.31.8
- 1.8UTF-8SCM-BSD
-
\ No newline at end of file
+
diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml
index 1be6036351..e5605878f8 100644
--- a/scm-clients/scm-cli-client/pom.xml
+++ b/scm-clients/scm-cli-client/pom.xml
@@ -168,7 +168,7 @@
org.eclipse.jettyjetty-maven-plugin
- ${jetty.version}
+ ${jetty.maven.version}8085STOP
diff --git a/scm-clients/scm-client-api/pom.xml b/scm-clients/scm-client-api/pom.xml
index 3a88bedcc2..d2f8692ca7 100644
--- a/scm-clients/scm-client-api/pom.xml
+++ b/scm-clients/scm-client-api/pom.xml
@@ -35,4 +35,4 @@
-
\ No newline at end of file
+
diff --git a/scm-clients/scm-client-api/src/main/java/sonia/scm/client/FileObjectWrapper.java b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/FileObjectWrapper.java
index 1c6c0aaeda..ddbeef69a3 100644
--- a/scm-clients/scm-client-api/src/main/java/sonia/scm/client/FileObjectWrapper.java
+++ b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/FileObjectWrapper.java
@@ -66,6 +66,7 @@ public class FileObjectWrapper
String revision, FileObject file)
{
this.repositoryBrowser = repositoryBrowser;
+ this.revision = revision;
this.file = file;
}
diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml
index 3a1f09ce40..68ff65317f 100644
--- a/scm-clients/scm-client-impl/pom.xml
+++ b/scm-clients/scm-client-impl/pom.xml
@@ -176,7 +176,7 @@
org.eclipse.jettyjetty-maven-plugin
- ${jetty.version}
+ ${jetty.maven.version}8085STOP
@@ -224,4 +224,4 @@
-
\ No newline at end of file
+
diff --git a/scm-core/pom.xml b/scm-core/pom.xml
index 5712172e07..f08de28f26 100644
--- a/scm-core/pom.xml
+++ b/scm-core/pom.xml
@@ -164,7 +164,7 @@
http://download.oracle.com/javase/6/docs/api/
http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/
http://jersey.java.net/nonav/apidocs/${jersey.version}/jersey/
- http://google-guice.googlecode.com/svn/tags/${guice.version}/javadoc/
+ https://google.github.io/guice/api-docs/${guice.version}/javadoc
http://www.slf4j.org/api/
http://shiro.apache.org/static/${shiro.version}/apidocs/
diff --git a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java
index 35dd2dcca0..f6507fc453 100644
--- a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java
+++ b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java
@@ -96,7 +96,7 @@ public class BasicContextProvider implements SCMContextProvider
version = loadVersion();
stage = loadProjectStage();
}
- catch (Throwable ex)
+ catch (Exception ex)
{
this.startupError = ex;
diff --git a/scm-core/src/main/java/sonia/scm/ModificationHandlerEvent.java b/scm-core/src/main/java/sonia/scm/ModificationHandlerEvent.java
new file mode 100644
index 0000000000..ce072ba7b1
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/ModificationHandlerEvent.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm;
+
+import sonia.scm.event.HandlerEvent;
+
+/**
+ * Extension to the {@link ModificationHandlerEvent}.
+ *
+ * @param type of changed item
+ *
+ * @author Sebastian Sdorra
+ * @since 1.48
+ */
+public interface ModificationHandlerEvent extends HandlerEvent
+{
+ /**
+ * Returns item, before it was modified.
+ *
+ * @return item before modification
+ */
+ public T getItemBeforeModification();
+}
diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java
index e6c96a903b..e0916f2460 100644
--- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java
+++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java
@@ -134,6 +134,7 @@ public class ScmConfiguration
this.skipFailedAuthenticators = other.skipFailedAuthenticators;
this.loginAttemptLimit = other.loginAttemptLimit;
this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout;
+ this.enabledXsrfProtection = other.enabledXsrfProtection;
}
//~--- get methods ----------------------------------------------------------
@@ -325,6 +326,19 @@ public class ScmConfiguration
return disableGroupingGrid;
}
+ /**
+ * Returns {@code true} if the cookie xsrf protection is enabled.
+ *
+ * @see Issue 793
+ * @return {@code true} if the cookie xsrf protection is enabled
+ *
+ * @since 1.47
+ */
+ public boolean isEnabledXsrfProtection()
+ {
+ return enabledXsrfProtection;
+ }
+
/**
* Returns true if proxy is enabled.
*
@@ -611,6 +625,19 @@ public class ScmConfiguration
this.skipFailedAuthenticators = skipFailedAuthenticators;
}
+ /**
+ * Set {@code true} to enable xsrf cookie protection.
+ *
+ * @param enabledXsrfProtection {@code true} to enable xsrf protection
+ * @see Issue 793
+ *
+ * @since 1.47
+ */
+ public void setEnabledXsrfProtection(boolean enabledXsrfProtection)
+ {
+ this.enabledXsrfProtection = enabledXsrfProtection;
+ }
+
//~--- fields ---------------------------------------------------------------
/** Field description */
@@ -700,4 +727,12 @@ public class ScmConfiguration
/** Field description */
private boolean anonymousAccessEnabled = false;
+
+ /**
+ * Enables xsrf cookie protection.
+ *
+ * @since 1.47
+ */
+ @XmlElement(name = "xsrf-protection")
+ private boolean enabledXsrfProtection = false;
}
diff --git a/scm-core/src/main/java/sonia/scm/group/AbstractGroupManager.java b/scm-core/src/main/java/sonia/scm/group/AbstractGroupManager.java
index 6715f322a7..12dcb8cd17 100644
--- a/scm-core/src/main/java/sonia/scm/group/AbstractGroupManager.java
+++ b/scm-core/src/main/java/sonia/scm/group/AbstractGroupManager.java
@@ -48,18 +48,18 @@ public abstract class AbstractGroupManager implements GroupManager
{
/**
- * Send a {@link GroupEvent} to the {@link ScmEventBus}.
+ * Creates a {@link GroupEvent} and send it to the {@link ScmEventBus}.
*
* @param event type of change event
* @param group group that has changed
*/
protected void fireEvent(HandlerEventType event, Group group)
{
- ScmEventBus.getInstance().post(new GroupEvent(event, group));
+ fireEvent(new GroupEvent(event, group));
}
/**
- * Send a {@link GroupEvent} to the {@link ScmEventBus}.
+ * Creates a {@link GroupModificationEvent} and send it to the {@link ScmEventBus}.
*
* @param event type of change event
* @param group group that has changed
@@ -67,6 +67,16 @@ public abstract class AbstractGroupManager implements GroupManager
*/
protected void fireEvent(HandlerEventType event, Group group, Group oldGroup)
{
- ScmEventBus.getInstance().post(new GroupEvent(event, group, oldGroup));
+ fireEvent(new GroupModificationEvent(event, group, oldGroup));
+ }
+
+ /**
+ * Sends a {@link GroupEvent} to the {@link ScmEventBus}.
+ *
+ * @param event group event
+ */
+ protected void fireEvent(GroupEvent event)
+ {
+ ScmEventBus.getInstance().post(event);
}
}
diff --git a/scm-core/src/main/java/sonia/scm/group/GroupEvent.java b/scm-core/src/main/java/sonia/scm/group/GroupEvent.java
index 33ef97d208..7328736ea7 100644
--- a/scm-core/src/main/java/sonia/scm/group/GroupEvent.java
+++ b/scm-core/src/main/java/sonia/scm/group/GroupEvent.java
@@ -47,7 +47,7 @@ import sonia.scm.event.Event;
* @since 1.23
*/
@Event
-public final class GroupEvent extends AbstractHandlerEvent
+public class GroupEvent extends AbstractHandlerEvent
{
/**
diff --git a/scm-core/src/main/java/sonia/scm/group/GroupModificationEvent.java b/scm-core/src/main/java/sonia/scm/group/GroupModificationEvent.java
new file mode 100644
index 0000000000..796800b14d
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/group/GroupModificationEvent.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.group;
+
+import sonia.scm.HandlerEventType;
+import sonia.scm.ModificationHandlerEvent;
+
+/**
+ * Event which is fired whenever a group is modified.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.48
+ */
+public class GroupModificationEvent extends GroupEvent implements ModificationHandlerEvent
+{
+
+ private final Group itemBeforeModification;
+
+ /**
+ * Constructs a new {@link GroupModificationEvent}.
+ *
+ * @param eventType type of event
+ * @param item changed group
+ * @param itemBeforeModification changed group before it was modified
+ */
+ public GroupModificationEvent(HandlerEventType eventType, Group item, Group itemBeforeModification)
+ {
+ super(eventType, item);
+ this.itemBeforeModification = itemBeforeModification;
+ }
+
+ @Override
+ public Group getItemBeforeModification()
+ {
+ return itemBeforeModification;
+ }
+
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/HttpClient.java b/scm-core/src/main/java/sonia/scm/net/HttpClient.java
index b9b891f557..6af89bcb71 100644
--- a/scm-core/src/main/java/sonia/scm/net/HttpClient.java
+++ b/scm-core/src/main/java/sonia/scm/net/HttpClient.java
@@ -48,7 +48,10 @@ import java.util.Map;
* @apiviz.landmark
* @apiviz.uses sonia.scm.net.HttpRequest
* @apiviz.uses sonia.scm.net.HttpResponse
+ *
+ * @deprecated use {@link sonia.scm.net.ahc.AdvancedHttpClient} instead.
*/
+@Deprecated
public interface HttpClient
{
diff --git a/scm-core/src/main/java/sonia/scm/net/HttpRequest.java b/scm-core/src/main/java/sonia/scm/net/HttpRequest.java
index f3ccf9719c..3f171a3fa7 100644
--- a/scm-core/src/main/java/sonia/scm/net/HttpRequest.java
+++ b/scm-core/src/main/java/sonia/scm/net/HttpRequest.java
@@ -51,7 +51,11 @@ import java.util.Map;
*
* @author Sebastian Sdorra
* @since 1.9
+ *
+ * @deprecated use {@link sonia.scm.net.ahc.AdvancedHttpRequest} or
+ * {@link sonia.scm.net.ahc.AdvancedHttpRequestWithBody} instead.
*/
+@Deprecated
public class HttpRequest
{
diff --git a/scm-core/src/main/java/sonia/scm/net/HttpResponse.java b/scm-core/src/main/java/sonia/scm/net/HttpResponse.java
index 83cdf9ee61..ef297c2809 100644
--- a/scm-core/src/main/java/sonia/scm/net/HttpResponse.java
+++ b/scm-core/src/main/java/sonia/scm/net/HttpResponse.java
@@ -41,12 +41,16 @@ import java.io.InputStream;
import java.util.List;
import java.util.Map;
+import sonia.scm.net.ahc.AdvancedHttpResponse;
/**
* Response of a {@link HttpRequest} execute by the {@link HttpClient}.
*
* @author Sebastian Sdorra
+ *
+ * @deprecated use {@link AdvancedHttpResponse} instead
*/
+@Deprecated
public interface HttpResponse extends Closeable
{
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java
new file mode 100644
index 0000000000..aa660f21ca
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.IOException;
+
+/**
+ * Advanced client for http operations. The {@link AdvancedHttpClient} replaces
+ * the much more simpler implementation {@link sonia.scm.net.HttpClient}. The
+ * {@link AdvancedHttpClient} offers a fluid interface for handling most common
+ * http operations. The {@link AdvancedHttpClient} can be injected by the
+ * default injection mechanism of SCM-Manager.
+ *
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ *
+ * @apiviz.landmark
+ */
+public abstract class AdvancedHttpClient
+{
+
+ /**
+ * Creates a {@link ContentTransformer} for the given Content-Type.
+ *
+ * @param type object type
+ * @param contentType content-type
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the content-type
+ *
+ * @return {@link ContentTransformer}
+ */
+ protected abstract ContentTransformer createTransformer(Class> type,
+ String contentType);
+
+ /**
+ * Executes the given request and returns the http response. Implementation
+ * have to check, if the instance if from type
+ * {@link AdvancedHttpRequestWithBody} in order to handle request contents.
+ *
+ *
+ * @param request request to execute
+ *
+ * @return http response
+ *
+ * @throws IOException
+ */
+ protected abstract AdvancedHttpResponse request(BaseHttpRequest> request)
+ throws IOException;
+
+ /**
+ * Returns a builder for a DELETE request.
+ *
+ *
+ * @param url request url
+ *
+ * @return request builder
+ */
+ public AdvancedHttpRequestWithBody delete(String url)
+ {
+ return new AdvancedHttpRequestWithBody(this, HttpMethod.DELETE, url);
+ }
+
+ /**
+ * Returns a builder for a HEAD request.
+ *
+ *
+ * @param url request url
+ *
+ * @return request builder
+ */
+ public AdvancedHttpRequest head(String url)
+ {
+ return new AdvancedHttpRequest(this, HttpMethod.HEAD, url);
+ }
+
+ /**
+ * Returns a request builder with a custom method. Note: not
+ * every method is supported by the underlying implementation of the http
+ * client.
+ *
+ *
+ * @param method http method
+ * @param url request url
+ *
+ * @return request builder
+ */
+ public AdvancedHttpRequestWithBody method(String method, String url)
+ {
+ return new AdvancedHttpRequestWithBody(this, method, url);
+ }
+
+ /**
+ * Returns a builder for a OPTIONS request.
+ *
+ *
+ * @param url request url
+ *
+ * @return request builder
+ */
+ public AdvancedHttpRequestWithBody options(String url)
+ {
+ return new AdvancedHttpRequestWithBody(this, HttpMethod.OPTIONS, url);
+ }
+
+ /**
+ * Returns a builder for a POST request.
+ *
+ *
+ * @param url request url
+ *
+ * @return request builder
+ */
+ public AdvancedHttpRequestWithBody post(String url)
+ {
+ return new AdvancedHttpRequestWithBody(this, HttpMethod.POST, url);
+ }
+
+ /**
+ * Returns a builder for a PUT request.
+ *
+ *
+ * @param url request url
+ *
+ * @return request builder
+ */
+ public AdvancedHttpRequestWithBody put(String url)
+ {
+ return new AdvancedHttpRequestWithBody(this, HttpMethod.PUT, url);
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Returns a builder for a GET request.
+ *
+ *
+ * @param url request url
+ *
+ * @return request builder
+ */
+ public AdvancedHttpRequest get(String url)
+ {
+ return new AdvancedHttpRequest(this, HttpMethod.GET, url);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequest.java
new file mode 100644
index 0000000000..b8940628b4
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequest.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- JDK imports ------------------------------------------------------------
+
+/**
+ * An http request without {@link Content} for example a GET or HEAD request.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class AdvancedHttpRequest extends BaseHttpRequest
+{
+
+ /**
+ * Constructs a new {@link AdvancedHttpRequest}
+ *
+ *
+ * @param client
+ * @param method
+ * @param url
+ */
+ AdvancedHttpRequest(AdvancedHttpClient client, String method,
+ String url)
+ {
+ super(client, method, url);
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Returns {@code this}.
+ *
+ *
+ * @return {@code this}
+ */
+ @Override
+ protected AdvancedHttpRequest self()
+ {
+ return this;
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java
new file mode 100644
index 0000000000..dd5ec6f425
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java
@@ -0,0 +1,271 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteSource;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.File;
+
+import java.nio.charset.Charset;
+
+/**
+ * Http request with body.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class AdvancedHttpRequestWithBody
+ extends BaseHttpRequest
+{
+
+ /**
+ * Constructs a new {@link AdvancedHttpRequestWithBody}.
+ *
+ *
+ * @param client http client
+ * @param method http method
+ * @param url url
+ */
+ AdvancedHttpRequestWithBody(AdvancedHttpClient client, String method,
+ String url)
+ {
+ super(client, method, url);
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Sets the content length for the request.
+ *
+ *
+ * @param length content length
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody contentLength(long length)
+ {
+ return header("Content-Length", String.valueOf(length));
+ }
+
+ /**
+ * Sets the content type for the request.
+ *
+ *
+ * @param contentType content type
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody contentType(String contentType)
+ {
+ return header("Content-Type", contentType);
+ }
+
+ /**
+ * Sets the content of the file as request content.
+ *
+ *
+ * @param file file
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody fileContent(File file)
+ {
+ this.content = new FileContent(file);
+
+ return this;
+ }
+
+ /**
+ * Returns a {@link FormContentBuilder}. The builder can be used to add form
+ * parameters as content for the request. Note: you have to
+ * call {@link FormContentBuilder#build()} in order to apply the form content
+ * to the request.
+ *
+ * @return form content builder
+ */
+ public FormContentBuilder formContent()
+ {
+ return new FormContentBuilder(this);
+ }
+
+ /**
+ * Transforms the given object to a xml string and set this string as request
+ * content.
+ *
+ * @param object object to transform
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the json content-type
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody jsonContent(Object object)
+ {
+ return transformedContent(ContentType.JSON, object);
+ }
+
+ /**
+ * Sets the raw data as request content.
+ *
+ *
+ * @param data raw data
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody rawContent(byte[] data)
+ {
+ this.content = new RawContent(data);
+
+ return this;
+ }
+
+ /**
+ * Sets the raw data as request content.
+ *
+ *
+ * @param source byte source
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody rawContent(ByteSource source)
+ {
+ this.content = new ByteSourceContent(source);
+
+ return this;
+ }
+
+ /**
+ * Sets the string as request content.
+ *
+ *
+ * @param content string content
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody stringContent(String content)
+ {
+ return stringContent(content, Charsets.UTF_8);
+ }
+
+ /**
+ * Sets the string as request content.
+ *
+ *
+ * @param content string content
+ * @param charset charset of content
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody stringContent(String content,
+ Charset charset)
+ {
+ this.content = new StringContent(content, charset);
+
+ return this;
+ }
+
+ /**
+ * Transforms the given object to a string and set this string as request
+ * content. The content-type is used to pick the right
+ * {@link ContentTransformer}. The method will throw an exception if no
+ * {@link ContentTransformer} for the content-type could be found.
+ *
+ * @param contentType content-type
+ * @param object object to transform
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the given content-type
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody transformedContent(String contentType,
+ Object object)
+ {
+ ContentTransformer transformer =
+ client.createTransformer(object.getClass(), contentType);
+ ByteSource value = transformer.marshall(object);
+
+ contentType(contentType);
+
+ return rawContent(value);
+ }
+
+ /**
+ * Transforms the given object to a xml string and set this string as request
+ * content.
+ *
+ * @param object object to transform
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the xml content-type
+ *
+ * @return {@code this}
+ */
+ public AdvancedHttpRequestWithBody xmlContent(Object object)
+ {
+ return transformedContent(ContentType.XML, object);
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Returns the content or the request.
+ *
+ *
+ * @return request content
+ */
+ public Content getContent()
+ {
+ return content;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Returns {@code this}.
+ *
+ *
+ * @return {@code this}
+ */
+ @Override
+ protected AdvancedHttpRequestWithBody self()
+ {
+ return this;
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** request content */
+ private Content content;
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java
new file mode 100644
index 0000000000..1cb087710b
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java
@@ -0,0 +1,312 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.io.ByteSource;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Http response. The response of a {@link AdvancedHttpRequest} or
+ * {@link AdvancedHttpRequestWithBody}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public abstract class AdvancedHttpResponse
+{
+
+ /**
+ * Returns the response content as byte source.
+ *
+ *
+ * @return response content as byte source
+ * @throws IOException
+ */
+ public abstract ByteSource contentAsByteSource() throws IOException;
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Returns the response headers.
+ *
+ *
+ * @return response headers
+ */
+ public abstract Multimap getHeaders();
+
+ /**
+ * Returns the status code of the response.
+ *
+ *
+ * @return status code
+ */
+ public abstract int getStatus();
+
+ /**
+ * Returns the status text of the response.
+ *
+ *
+ * @return status text
+ */
+ public abstract String getStatusText();
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Creates a {@link ContentTransformer} for the given Content-Type.
+ *
+ * @param type object type
+ * @param contentType content-type
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the content-type
+ *
+ * @return {@link ContentTransformer}
+ */
+ protected abstract ContentTransformer createTransformer(Class> type,
+ String contentType);
+
+ /**
+ * Returns the content of the response as byte array.
+ *
+ *
+ * @return content as byte array
+ *
+ * @throws IOException
+ */
+ public byte[] content() throws IOException
+ {
+ ByteSource content = contentAsByteSource();
+ byte[] data = null;
+
+ if (content != null)
+ {
+ data = content.read();
+ }
+
+ return data;
+ }
+
+ /**
+ * Returns a reader for the content of the response.
+ *
+ *
+ * @return read of response content
+ *
+ * @throws IOException
+ */
+ public BufferedReader contentAsReader() throws IOException
+ {
+ ByteSource content = contentAsByteSource();
+ BufferedReader reader = null;
+
+ if (content != null)
+ {
+ reader = content.asCharSource(Charsets.UTF_8).openBufferedStream();
+ }
+
+ return reader;
+ }
+
+ /**
+ * Returns response content as stream.
+ *
+ *
+ * @return response content as stram
+ *
+ * @throws IOException
+ */
+ public InputStream contentAsStream() throws IOException
+ {
+ ByteSource content = contentAsByteSource();
+ InputStream stream = null;
+
+ if (content != null)
+ {
+ stream = content.openBufferedStream();
+ }
+
+ return stream;
+ }
+
+ /**
+ * Returns the response content as string.
+ *
+ *
+ * @return response content
+ *
+ * @throws IOException
+ */
+ public String contentAsString() throws IOException
+ {
+ ByteSource content = contentAsByteSource();
+ String value = null;
+
+ if (content != null)
+ {
+ value = content.asCharSource(Charsets.UTF_8).read();
+ }
+
+ return value;
+ }
+
+ /**
+ * Transforms the response content from json to the given type.
+ *
+ * @param object type
+ * @param type object type
+ *
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the json content-type
+ *
+ * @return transformed object
+ *
+ * @throws IOException
+ */
+ public T contentFromJson(Class type) throws IOException
+ {
+ return contentTransformed(type, ContentType.JSON);
+ }
+
+ /**
+ * Transforms the response content from xml to the given type.
+ *
+ * @param object type
+ * @param type object type
+ *
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the xml content-type
+ *
+ * @return transformed object
+ *
+ * @throws IOException
+ */
+ public T contentFromXml(Class type) throws IOException
+ {
+ return contentTransformed(type, ContentType.XML);
+ }
+
+ /**
+ * Transforms the response content to the given type. The method uses the
+ * content-type header to pick the right {@link ContentTransformer}.
+ *
+ * @param object type
+ * @param type object type
+ *
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the content-type
+ *
+ * @return transformed object
+ *
+ * @throws IOException
+ */
+ public T contentTransformed(Class type) throws IOException
+ {
+ String contentType = getFirstHeader("Content-Type");
+
+ if (Strings.isNullOrEmpty(contentType))
+ {
+ throw new ContentTransformerException(
+ "response does not return a Content-Type header");
+ }
+
+ return contentTransformed(type, contentType);
+ }
+
+ /**
+ * Transforms the response content from xml to the given type.
+ *
+ * @param object type
+ * @param type object type
+ * @param contentType type to pick {@link ContentTransformer}
+ *
+ * @throws ContentTransformerNotFoundException if no
+ * {@link ContentTransformer} could be found for the content-type
+ *
+ * @return transformed object
+ *
+ * @throws IOException
+ */
+ public T contentTransformed(Class type, String contentType)
+ throws IOException
+ {
+ T object = null;
+ ByteSource source = contentAsByteSource();
+
+ if (source != null)
+ {
+ ContentTransformer transformer = createTransformer(type, contentType);
+
+ object = transformer.unmarshall(type, contentAsByteSource());
+ }
+
+ return object;
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Returns the first header value for the given header name or {@code null}.
+ *
+ *
+ * @param name header name
+ *
+ * @return header value or {@code null}
+ */
+ public String getFirstHeader(String name)
+ {
+ return Iterables.getFirst(getHeaders().get(name), null);
+ }
+
+ /**
+ * Returns {@code true} if the response was successful. A response is
+ * successful, if the status code is greater than 199 and lower than 400.
+ *
+ * @return {@code true} if the response was successful
+ */
+ public boolean isSuccessful()
+ {
+ int status = getStatus();
+
+ return (status > 199) && (status < 400);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java
new file mode 100644
index 0000000000..c80ae9b1c4
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java
@@ -0,0 +1,409 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+
+import org.apache.shiro.codec.Base64;
+
+import sonia.scm.util.HttpUtil;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.IOException;
+
+/**
+ * Base class for http requests.
+ *
+ * @author Sebastian Sdorra
+ * @param request implementation
+ *
+ * @since 1.46
+ */
+public abstract class BaseHttpRequest
+{
+
+ /**
+ * Constructs a new {@link BaseHttpRequest}.
+ *
+ *
+ * @param client http client
+ * @param method http method
+ * @param url url
+ */
+ public BaseHttpRequest(AdvancedHttpClient client, String method, String url)
+ {
+ this.client = client;
+ this.method = method;
+ this.url = url;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Executes the request and returns the http response.
+ *
+ *
+ * @return http response
+ *
+ * @throws IOException
+ */
+ public AdvancedHttpResponse request() throws IOException
+ {
+ return client.request(this);
+ }
+
+ /**
+ * Implementing classes should return {@code this}.
+ *
+ *
+ * @return request instance
+ */
+ protected abstract T self();
+
+ /**
+ * Enabled http basic authentication.
+ *
+ *
+ * @param username username for http basic authentication
+ * @param password password for http basic authentication
+ *
+ * @return http request instance
+ */
+ public T basicAuth(String username, String password)
+ {
+ String auth = Strings.nullToEmpty(username).concat(":").concat(
+ Strings.nullToEmpty(password));
+
+ auth = Base64.encodeToString(auth.getBytes(Charsets.ISO_8859_1));
+ headers.put("Authorization", "Basic ".concat(auth));
+
+ return self();
+ }
+
+ /**
+ * Enable or disabled gzip decoding. The default value is false.
+ *
+ *
+ * @param decodeGZip true to enable gzip decoding
+ *
+ * @return request instance
+ */
+ public T decodeGZip(boolean decodeGZip)
+ {
+ this.decodeGZip = decodeGZip;
+
+ return self();
+ }
+
+ /**
+ * Enable or disable certificate validation of ssl certificates. The default
+ * value is false.
+ *
+ *
+ * @param disableCertificateValidation true to disable certificate validation
+ *
+ * @return request instance
+ */
+ public T disableCertificateValidation(boolean disableCertificateValidation)
+ {
+ this.disableCertificateValidation = disableCertificateValidation;
+
+ return self();
+ }
+
+ /**
+ * Enable or disable the validation of ssl hostnames. The default value is
+ * false.
+ *
+ *
+ * @param disableHostnameValidation true to disable ssl hostname validation
+ *
+ * @return request instance
+ */
+ public T disableHostnameValidation(boolean disableHostnameValidation)
+ {
+ this.disableHostnameValidation = disableHostnameValidation;
+
+ return self();
+ }
+
+ /**
+ * Add http headers to request.
+ *
+ *
+ * @param name header name
+ * @param values header values
+ *
+ * @return request instance
+ */
+ public T header(String name, Object... values)
+ {
+ for (Object v : values)
+ {
+ headers.put(name, toString(v));
+ }
+
+ return self();
+ }
+
+ /**
+ * Add http headers to request.
+ *
+ *
+ * @param name header name
+ * @param values header values
+ *
+ * @return request instance
+ */
+ public T headers(String name, Iterable extends Object> values)
+ {
+ for (Object v : values)
+ {
+ headers.put(name, toString(v));
+ }
+
+ return self();
+ }
+
+ /**
+ * Ignore proxy settings. The default value is false.
+ *
+ *
+ * @param ignoreProxySettings true to ignore proxy settings.
+ *
+ * @return request instance
+ */
+ public T ignoreProxySettings(boolean ignoreProxySettings)
+ {
+ this.ignoreProxySettings = ignoreProxySettings;
+
+ return self();
+ }
+
+ /**
+ * Appends a query parameter to the request.
+ *
+ *
+ * @param name name of query parameter
+ * @param values query parameter values
+ *
+ * @return request instance
+ */
+ public T queryString(String name, Object... values)
+ {
+ for (Object v : values)
+ {
+ appendQueryString(name, v);
+ }
+
+ return self();
+ }
+
+ /**
+ * Appends a query parameter to the request.
+ *
+ *
+ * @param name name of query parameter
+ * @param values query parameter values
+ *
+ * @return request instance
+ */
+ public T queryStrings(String name, Iterable extends Object> values)
+ {
+ for (Object v : values)
+ {
+ appendQueryString(name, v);
+ }
+
+ return self();
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Return a map with http headers used for the request.
+ *
+ *
+ * @return map with http headers
+ */
+ public Multimap getHeaders()
+ {
+ return headers;
+ }
+
+ /**
+ * Returns the http method for the request.
+ *
+ *
+ * @return http method of request
+ */
+ public String getMethod()
+ {
+ return method;
+ }
+
+ /**
+ * Returns the url for the request.
+ *
+ *
+ * @return url of the request
+ */
+ public String getUrl()
+ {
+ return url;
+ }
+
+ /**
+ * Returns true if the request decodes gzip compression.
+ *
+ *
+ * @return true if the request decodes gzip compression
+ */
+ public boolean isDecodeGZip()
+ {
+ return decodeGZip;
+ }
+
+ /**
+ * Returns true if the verification of ssl certificates is disabled.
+ *
+ *
+ * @return true if certificate verification is disabled
+ */
+ public boolean isDisableCertificateValidation()
+ {
+ return disableCertificateValidation;
+ }
+
+ /**
+ * Returns true if the ssl hostname validation is disabled.
+ *
+ *
+ * @return true if the ssl hostname validation is disabled
+ */
+ public boolean isDisableHostnameValidation()
+ {
+ return disableHostnameValidation;
+ }
+
+ /**
+ * Returns true if the proxy settings are ignored.
+ *
+ *
+ * @return true if the proxy settings are ignored
+ */
+ public boolean isIgnoreProxySettings()
+ {
+ return ignoreProxySettings;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Returns the value url encoded.
+ *
+ *
+ * @param value value to encode
+ *
+ * @return encoded value
+ */
+ protected String encoded(Object value)
+ {
+ return HttpUtil.encode(Strings.nullToEmpty(toString(value)));
+ }
+
+ /**
+ * Returns string representation of the given object or {@code null}, if the
+ * object is {@code null}.
+ *
+ *
+ * @param object object
+ *
+ * @return string representation or {@code null}
+ */
+ protected String toString(Object object)
+ {
+ return (object != null)
+ ? object.toString()
+ : null;
+ }
+
+ private void appendQueryString(String name, Object value)
+ {
+ StringBuilder buffer = new StringBuilder();
+
+ if (url.contains("?"))
+ {
+ buffer.append("&");
+ }
+ else
+ {
+ buffer.append("?");
+ }
+
+ buffer.append(encoded(name)).append("=").append(encoded(value));
+ url = url.concat(buffer.toString());
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** http client */
+ protected final AdvancedHttpClient client;
+
+ /** http header */
+ private final Multimap headers = LinkedHashMultimap.create();
+
+ /** http method */
+ private final String method;
+
+ /** ignore proxy settings */
+ private boolean ignoreProxySettings = false;
+
+ /** disable ssl hostname validation */
+ private boolean disableHostnameValidation = false;
+
+ /** disable ssl certificate validation */
+ private boolean disableCertificateValidation = false;
+
+ /** decode gzip */
+ private boolean decodeGZip = false;
+
+ /** url of request */
+ private String url;
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ByteSourceContent.java b/scm-core/src/main/java/sonia/scm/net/ahc/ByteSourceContent.java
new file mode 100644
index 0000000000..f4182c0975
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ByteSourceContent.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.io.ByteSource;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * {@link ByteSource} content for {@link AdvancedHttpRequestWithBody}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class ByteSourceContent implements Content
+{
+
+ /**
+ * Constructs a new {@link ByteSourceContent}.
+ *
+ *
+ * @param byteSource byte source
+ */
+ public ByteSourceContent(ByteSource byteSource)
+ {
+ this.byteSource = byteSource;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Sets the content length for the request.
+ *
+ *
+ * @param request http request
+ *
+ * @throws IOException
+ */
+ @Override
+ public void prepare(AdvancedHttpRequestWithBody request) throws IOException
+ {
+ request.contentLength(byteSource.size());
+ }
+
+ /**
+ * Copies the content of the byte source to the output stream.
+ *
+ *
+ * @param output output stream
+ *
+ * @throws IOException
+ */
+ @Override
+ public void process(OutputStream output) throws IOException
+ {
+ byteSource.copyTo(output);
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** byte source */
+ private final ByteSource byteSource;
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/Content.java b/scm-core/src/main/java/sonia/scm/net/ahc/Content.java
new file mode 100644
index 0000000000..0f73e7b0d6
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/Content.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Content of a {@link AdvancedHttpRequestWithBody}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public interface Content
+{
+
+ /**
+ * Prepares the {@link AdvancedHttpRequestWithBody} for the request content.
+ * Implementations can set the content type, content length or custom headers
+ * for the request.
+ *
+ *
+ * @param request request
+ *
+ * @throws IOException
+ */
+ public void prepare(AdvancedHttpRequestWithBody request) throws IOException;
+
+ /**
+ * Copies the content to the output stream.
+ *
+ *
+ * @param output output stream
+ *
+ * @throws IOException
+ */
+ public void process(OutputStream output) throws IOException;
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformer.java b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformer.java
new file mode 100644
index 0000000000..0902e2ff79
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformer.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+import com.google.common.io.ByteSource;
+import sonia.scm.plugin.ExtensionPoint;
+
+/**
+ * Transforms {@link ByteSource} content to an object and vice versa. This class
+ * is an extension point, this means that plugins can define their own
+ * {@link ContentTransformer} implementations by implementing the interface and
+ * annotate the implementation with the {@link sonia.scm.plugin.ext.Extension}
+ * annotation.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+@ExtensionPoint
+public interface ContentTransformer
+{
+
+ /**
+ * Returns {@code true} if the transformer is responsible for the given
+ * object and content type.
+ *
+ * @param type object type
+ * @param contentType content type
+ *
+ * @return {@code true} if the transformer is responsible
+ */
+ public boolean isResponsible(Class> type, String contentType);
+
+ /**
+ * Marshalls the given object into a {@link ByteSource}.
+ *
+ *
+ * @param object object to marshall
+ *
+ * @return string content
+ */
+ public ByteSource marshall(Object object);
+
+ /**
+ * Unmarshall the {@link ByteSource} content to an object of the given type.
+ *
+ *
+ * @param type type of result object
+ * @param content content
+ * @param type of result object
+ *
+ * @return
+ */
+ public T unmarshall(Class type, ByteSource content);
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerException.java b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerException.java
new file mode 100644
index 0000000000..d4e2299206
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerException.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+/**
+ * A {@link ContentTransformerException} is thrown if an error occurs during
+ * content transformation by an {@link ContentTransformer}.
+ *
+ * @author Sebastian Sdorra
+ *
+ * @since 1.46
+ */
+public class ContentTransformerException extends RuntimeException
+{
+
+ /** Field description */
+ private static final long serialVersionUID = 367333504151208563L;
+
+ //~--- constructors ---------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ContentTransformerException}.
+ *
+ *
+ * @param message exception message
+ */
+ public ContentTransformerException(String message)
+ {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@link ContentTransformerException}.
+ *
+ *
+ * @param message exception message
+ * @param cause exception cause
+ */
+ public ContentTransformerException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerNotFoundException.java b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerNotFoundException.java
new file mode 100644
index 0000000000..a8e85cdaf2
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerNotFoundException.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+/**
+ * The ContentTransformerNotFoundException is thrown, if no
+ * {@link ContentTransformer} could be found for the given type.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class ContentTransformerNotFoundException
+ extends ContentTransformerException
+{
+
+ /** Field description */
+ private static final long serialVersionUID = 6374525163951460938L;
+
+ //~--- constructors ---------------------------------------------------------
+
+ /**
+ * Constructs a new ContentTransformerNotFoundException.
+ *
+ *
+ * @param message exception message
+ */
+ public ContentTransformerNotFoundException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ContentType.java b/scm-core/src/main/java/sonia/scm/net/ahc/ContentType.java
new file mode 100644
index 0000000000..8e3b299f96
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ContentType.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+/**
+ * Content-Types.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public final class ContentType
+{
+
+ /** json content type */
+ public static final String JSON = "application/json";
+
+ /** xml content type */
+ public static final String XML = "application/xml";
+
+ //~--- constructors ---------------------------------------------------------
+
+ private ContentType() {}
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/FileContent.java b/scm-core/src/main/java/sonia/scm/net/ahc/FileContent.java
new file mode 100644
index 0000000000..51f1af6c21
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/FileContent.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Sets the content of the file to the request.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class FileContent implements Content
+{
+
+ /**
+ * Constructs a new {@link FileContent}.
+ *
+ *
+ * @param file file
+ */
+ public FileContent(File file)
+ {
+ this.file = file;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Sets the content length of the file as request header.
+ *
+ *
+ * @param request request
+ */
+ @Override
+ public void prepare(AdvancedHttpRequestWithBody request)
+ {
+ request.contentLength(file.length());
+ }
+
+ /**
+ * Copies the content of the file to the output stream.
+ *
+ *
+ * @param output output stream
+ *
+ * @throws IOException
+ */
+ @Override
+ public void process(OutputStream output) throws IOException
+ {
+ InputStream stream = null;
+
+ try
+ {
+ stream = new FileInputStream(file);
+ ByteStreams.copy(stream, output);
+ }
+ finally
+ {
+ Closeables.close(stream, true);
+ }
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** file */
+ private final File file;
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/FormContentBuilder.java b/scm-core/src/main/java/sonia/scm/net/ahc/FormContentBuilder.java
new file mode 100644
index 0000000000..d25c12ce94
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/FormContentBuilder.java
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Strings;
+
+import sonia.scm.util.HttpUtil;
+
+/**
+ * The form builder is able to add form parameters to a request.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class FormContentBuilder
+{
+
+ /**
+ * Constructs a new {@link FormContentBuilder}.
+ *
+ *
+ * @param request request
+ */
+ public FormContentBuilder(AdvancedHttpRequestWithBody request)
+ {
+ this.request = request;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Build the formular content and append it to the request.
+ *
+ *
+ * @return request instance
+ */
+ public AdvancedHttpRequestWithBody build()
+ {
+ request.contentType("application/x-www-form-urlencoded");
+ request.stringContent(builder.toString());
+
+ return request;
+ }
+
+ /**
+ * Adds a formular parameter.
+ *
+ *
+ * @param name parameter name
+ * @param values parameter values
+ *
+ * @return {@code this}
+ */
+ public FormContentBuilder fields(String name, Iterable extends Object> values)
+ {
+ for (Object v : values)
+ {
+ append(name, v);
+ }
+
+ return this;
+ }
+
+ /**
+ * Adds a formular parameter.
+ *
+ *
+ * @param name parameter name
+ * @param values parameter values
+ *
+ * @return {@code this}
+ */
+ public FormContentBuilder field(String name, Object... values)
+ {
+ for (Object v : values)
+ {
+ append(name, v);
+ }
+
+ return this;
+ }
+
+ private void append(String name, Object value)
+ {
+ if (!Strings.isNullOrEmpty(name))
+ {
+ if (builder.length() > 0)
+ {
+ builder.append("&");
+ }
+
+ builder.append(HttpUtil.encode(name)).append("=");
+
+ if (value != null)
+ {
+ builder.append(HttpUtil.encode(value.toString()));
+ }
+ }
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** content builder */
+ private final StringBuilder builder = new StringBuilder();
+
+ /** request */
+ private final AdvancedHttpRequestWithBody request;
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/HttpMethod.java b/scm-core/src/main/java/sonia/scm/net/ahc/HttpMethod.java
new file mode 100644
index 0000000000..41a23dcccb
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/HttpMethod.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+/**
+ * Http methods.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public final class HttpMethod
+{
+
+ /** http delete method */
+ public static final String DELETE = "DELETE";
+
+ /** http get method */
+ public static final String GET = "GET";
+
+ /** http head method */
+ public static final String HEAD = "HEAD";
+
+ /** http options method */
+ public static final String OPTIONS = "OPTIONS";
+
+ /** http post method */
+ public static final String POST = "POST";
+
+ /** http put method */
+ public static final String PUT = "PUT";
+
+ //~--- constructors ---------------------------------------------------------
+
+ private HttpMethod() {}
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/RawContent.java b/scm-core/src/main/java/sonia/scm/net/ahc/RawContent.java
new file mode 100644
index 0000000000..0b5e99bc49
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/RawContent.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.annotations.VisibleForTesting;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Byte array content for {@link AdvancedHttpRequestWithBody}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class RawContent implements Content
+{
+
+ /**
+ * Constructs a new {@link RawContent}.
+ *
+ *
+ * @param data data
+ */
+ public RawContent(byte[] data)
+ {
+ this.data = data;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Sets the length of the byte array as http header.
+ *
+ *
+ * @param request request
+ */
+ @Override
+ public void prepare(AdvancedHttpRequestWithBody request)
+ {
+ request.contentLength(data.length);
+ }
+
+ /**
+ * Writes the byte array to the output stream.
+ *
+ *
+ * @param output output stream
+ *
+ * @throws IOException
+ */
+ @Override
+ public void process(OutputStream output) throws IOException
+ {
+ output.write(data);
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Returns content data.
+ *
+ *
+ * @return content data
+ */
+ @VisibleForTesting
+ byte[] getData()
+ {
+ return data;
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** byte array */
+ private final byte[] data;
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/StringContent.java b/scm-core/src/main/java/sonia/scm/net/ahc/StringContent.java
new file mode 100644
index 0000000000..07473447a7
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/StringContent.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.nio.charset.Charset;
+
+/**
+ * String content for {@link AdvancedHttpRequestWithBody}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class StringContent extends RawContent
+{
+
+ /**
+ * Constructs a new {@link StringContent}.
+ *
+ *
+ * @param content content
+ * @param charset charset
+ */
+ public StringContent(String content, Charset charset)
+ {
+ super(content.getBytes(charset));
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/package-info.java b/scm-core/src/main/java/sonia/scm/net/ahc/package-info.java
new file mode 100644
index 0000000000..d11a603b5a
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/package-info.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+/**
+ * Advanced http client.
+ */
+package sonia.scm.net.ahc;
diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryManager.java b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryManager.java
index d6da4ec43c..418316e7e7 100644
--- a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryManager.java
+++ b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryManager.java
@@ -80,7 +80,7 @@ public abstract class AbstractRepositoryManager implements RepositoryManager
protected void fireEvent(HandlerEventType event, Repository repository,
Repository oldRepository)
{
- ScmEventBus.getInstance().post(new RepositoryEvent(event, repository,
+ ScmEventBus.getInstance().post(new RepositoryModificationEvent(event, repository,
oldRepository));
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/Branch.java b/scm-core/src/main/java/sonia/scm/repository/Branch.java
index 9c16259d91..ac8a064c71 100644
--- a/scm-core/src/main/java/sonia/scm/repository/Branch.java
+++ b/scm-core/src/main/java/sonia/scm/repository/Branch.java
@@ -73,12 +73,28 @@ public final class Branch implements Serializable
*
*
* @param name name of the branch
+ *
+ * @deprecated use {@link Branch#Branch(String, String)} instead
*/
+ @Deprecated
public Branch(String name)
{
this.name = name;
}
+ /**
+ * Constructs a new branch.
+ *
+ *
+ * @param name name of the branch
+ * @param revision latest revision of the branch
+ */
+ public Branch(String name, String revision)
+ {
+ this.name = name;
+ this.revision = revision;
+ }
+
//~--- methods --------------------------------------------------------------
/**
@@ -104,7 +120,8 @@ public final class Branch implements Serializable
final Branch other = (Branch) obj;
- return Objects.equal(name, other.name);
+ return Objects.equal(name, other.name)
+ && Objects.equal(revision, other.revision);
}
/**
@@ -116,7 +133,7 @@ public final class Branch implements Serializable
@Override
public int hashCode()
{
- return Objects.hashCode(name);
+ return Objects.hashCode(name, revision);
}
/**
@@ -131,6 +148,7 @@ public final class Branch implements Serializable
//J-
return Objects.toStringHelper(this)
.add("name", name)
+ .add("revision", revision)
.toString();
//J+
}
@@ -148,8 +166,21 @@ public final class Branch implements Serializable
return name;
}
+ /**
+ * Returns the latest revision of the branch.
+ *
+ * @return latest revision of branch
+ */
+ public String getRevision()
+ {
+ return revision;
+ }
+
//~--- fields ---------------------------------------------------------------
/** name of the branch */
private String name;
+
+ /** Field description */
+ private String revision;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java
index dc12dc382f..fecfd74cc9 100644
--- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java
+++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java
@@ -132,7 +132,9 @@ public class Changeset extends BasicPropertiesAware
final Changeset other = (Changeset) obj;
- return Objects.equal(id, other.id) && Objects.equal(date, other.date)
+ //J-
+ return Objects.equal(id, other.id)
+ && Objects.equal(date, other.date)
&& Objects.equal(author, other.author)
&& Objects.equal(description, other.description)
&& Objects.equal(parents, other.parents)
@@ -140,6 +142,7 @@ public class Changeset extends BasicPropertiesAware
&& Objects.equal(branches, other.branches)
&& Objects.equal(modifications, other.modifications)
&& Objects.equal(properties, other.properties);
+ //J+
}
/**
@@ -206,7 +209,9 @@ public class Changeset extends BasicPropertiesAware
}
/**
- * Returns the branches of the changeset.
+ * Returns the branches of the changeset. In the most cases a changeset is
+ * only related to one branch, but in the case of receive hooks it is possible
+ * that a changeset is related to more than a branch.
*
*
* @return branches of the changeset
diff --git a/scm-core/src/main/java/sonia/scm/repository/ClearRepositoryCacheEvent.java b/scm-core/src/main/java/sonia/scm/repository/ClearRepositoryCacheEvent.java
new file mode 100644
index 0000000000..13c228bd34
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/ClearRepositoryCacheEvent.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.repository;
+
+import sonia.scm.event.Event;
+
+/**
+ * Event which causes clearing of repository cache.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.50
+ */
+@Event
+public class ClearRepositoryCacheEvent {
+
+ private final Repository repository;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param repository repository
+ */
+ public ClearRepositoryCacheEvent(Repository repository) {
+ this.repository = repository;
+ }
+
+ /**
+ * Returns repository.
+ *
+ * @return repository
+ */
+ public Repository getRepository() {
+ return repository;
+ }
+
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/Permission.java b/scm-core/src/main/java/sonia/scm/repository/Permission.java
index 60c6628d61..b5de810f75 100644
--- a/scm-core/src/main/java/sonia/scm/repository/Permission.java
+++ b/scm-core/src/main/java/sonia/scm/repository/Permission.java
@@ -136,8 +136,9 @@ public class Permission implements PermissionObject, Serializable
final Permission other = (Permission) obj;
- return Objects.equal(name, other.name) && Objects.equal(type, other.type)
- && Objects.equal(groupPermission, groupPermission);
+ return Objects.equal(name, other.name)
+ && Objects.equal(type, other.type)
+ && Objects.equal(groupPermission, other.groupPermission);
}
/**
diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryEvent.java
index 8fad49a5d9..0e50ef10bc 100644
--- a/scm-core/src/main/java/sonia/scm/repository/RepositoryEvent.java
+++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryEvent.java
@@ -35,12 +35,10 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
-import com.google.common.base.Objects;
import sonia.scm.HandlerEventType;
import sonia.scm.event.AbstractHandlerEvent;
import sonia.scm.event.Event;
-import sonia.scm.event.HandlerEvent;
/**
* The RepositoryEvent is fired if a {@link Repository} object changes.
@@ -49,7 +47,7 @@ import sonia.scm.event.HandlerEvent;
* @since 1.23
*/
@Event
-public final class RepositoryEvent extends AbstractHandlerEvent
+public class RepositoryEvent extends AbstractHandlerEvent
{
/**
diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryModificationEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryModificationEvent.java
new file mode 100644
index 0000000000..45e6ffd646
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryModificationEvent.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.repository;
+
+import sonia.scm.HandlerEventType;
+import sonia.scm.ModificationHandlerEvent;
+import sonia.scm.event.Event;
+
+/**
+ * Event which is fired whenever a repository is modified.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.48
+ */
+@Event
+public final class RepositoryModificationEvent extends RepositoryEvent implements ModificationHandlerEvent
+{
+
+ private final Repository itemBeforeModification;
+
+ /**
+ * Constructs a new {@link RepositoryModificationEvent}.
+ *
+ * @param eventType event type
+ * @param item changed repository
+ * @param itemBeforeModification repository before it was modified
+ */
+ public RepositoryModificationEvent(HandlerEventType eventType, Repository item, Repository itemBeforeModification)
+ {
+ super(eventType, item);
+ this.itemBeforeModification = itemBeforeModification;
+ }
+
+ @Override
+ public Repository getItemBeforeModification()
+ {
+ return itemBeforeModification;
+ }
+
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookBranchProvider.java b/scm-core/src/main/java/sonia/scm/repository/api/HookBranchProvider.java
new file mode 100644
index 0000000000..3ad52b995c
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/api/HookBranchProvider.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.repository.api;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.util.List;
+
+/**
+ * The HookBranchProvider returns informations about branch changes during the
+ * current hook.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.45
+ */
+public interface HookBranchProvider
+{
+
+ /**
+ * Returns the list of created or modified branch names.
+ *
+ * @return list of created or modified branches
+ */
+ public List getCreatedOrModified();
+
+ /**
+ * Returns the list deleted or closed branch names.
+ *
+ * @return list of deleted or closed branches
+ */
+ public List getDeletedOrClosed();
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java b/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java
index b7e3e9c5e6..a7c7e8354e 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java
@@ -43,8 +43,9 @@ import sonia.scm.repository.spi.HookContextProvider;
/**
* The context for all repository hooks. With the {@link HookContext} class it
- * is able to send messages back to the client and retrieve {@link Changeset}s
- * which are added during this push/commit.
+ * is able to send messages back to the client, retrieve {@link Changeset}s
+ * which are added during this push/commit and gives informations about changed
+ * branches and tags.
*
* @author Sebastian Sdorra
* @since 1.33
@@ -78,12 +79,59 @@ public final class HookContext
//~--- get methods ----------------------------------------------------------
+ /**
+ * Returns a {@link HookBranchProvider} which is able to return informations
+ * about changed branches during the current hook.
+ *
+ * @return {@link HookBranchProvider}
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ * by the underlying provider
+ *
+ * @since 1.45
+ */
+ public HookBranchProvider getBranchProvider()
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("create branch provider for repository {}",
+ repository.getName());
+ }
+
+ return provider.getBranchProvider();
+ }
+
+ /**
+ * Returns a {@link HookTagProvider} which is able to return informations
+ * about changed tags during the current hook.
+ *
+ * @return {@link HookTagProvider}
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ * by the underlying provider
+ *
+ * @since 1.50
+ */
+ public HookTagProvider getTagProvider()
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("create tag provider for repository {}",
+ repository.getName());
+ }
+
+ return provider.getTagProvider();
+ }
+
/**
* Returns a {@link HookChangesetBuilder} which is able to return all
* {@link Changeset}'s during this push/commit.
*
*
* @return {@link HookChangesetBuilder}
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ * by the underlying provider
*/
public HookChangesetBuilder getChangesetProvider()
{
@@ -111,6 +159,9 @@ public final class HookContext
*
* @return {@link HookMessageProvider} which is able to send message back to
* the scm client
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ * by the underlying provider
*/
public HookMessageProvider getMessageProvider()
{
@@ -138,12 +189,12 @@ public final class HookContext
//~--- fields ---------------------------------------------------------------
- /** Field description */
- private PreProcessorUtil preProcessorUtil;
+ /** pre processor util */
+ private final PreProcessorUtil preProcessorUtil;
- /** Field description */
- private HookContextProvider provider;
+ /** hook context provider */
+ private final HookContextProvider provider;
- /** Field description */
- private Repository repository;
+ /** repository */
+ private final Repository repository;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java b/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java
index 082f315a35..15982022de 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java
@@ -37,4 +37,30 @@ package sonia.scm.repository.api;
* @author Sebastian Sdorra
* @since 1.33
*/
-public enum HookFeature { MESSAGE_PROVIDER, CHANGESET_PROVIDER; }
+public enum HookFeature
+{
+
+ /**
+ * Hook message provider
+ */
+ MESSAGE_PROVIDER,
+
+ /**
+ * Hook changeset provider
+ */
+ CHANGESET_PROVIDER,
+
+ /**
+ * Hook branch provider
+ *
+ * @since 1.45
+ */
+ BRANCH_PROVIDER,
+
+ /**
+ * Hook tag provider
+ *
+ * @since 1.50
+ */
+ TAG_PROVIDER;
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookTagProvider.java b/scm-core/src/main/java/sonia/scm/repository/api/HookTagProvider.java
new file mode 100644
index 0000000000..695cba9d2d
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/api/HookTagProvider.java
@@ -0,0 +1,59 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.repository.api;
+
+import java.util.List;
+import sonia.scm.repository.Tag;
+
+/**
+ * The HookTagProvider returns informations about tags during the
+ * current hook.
+ *
+ * @since 1.50
+ * @author Sebastian Sdorra
+ */
+public interface HookTagProvider {
+
+ /**
+ * Return all tags which are delivered during the current hook.
+ *
+ * @return all tags of current hook
+ */
+ public List getCreatedTags();
+
+ /**
+ * Return all tags which are deleted during the current hook.
+ *
+ * @return all deleted tags of current hook
+ */
+ public List getDeletedTags();
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java
index a8fbbaa69d..e07eee44f0 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java
@@ -40,6 +40,7 @@ import com.github.legman.Subscribe;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -50,11 +51,6 @@ import sonia.scm.HandlerEventType;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.config.ScmConfiguration;
-import sonia.scm.event.ScmEventBus;
-import sonia.scm.repository.BlameResult;
-import sonia.scm.repository.Branches;
-import sonia.scm.repository.BrowserResult;
-import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
import sonia.scm.repository.PreProcessorUtil;
import sonia.scm.repository.Repository;
@@ -63,7 +59,6 @@ import sonia.scm.repository.RepositoryEvent;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RepositoryPermissions;
-import sonia.scm.repository.Tags;
import sonia.scm.repository.spi.RepositoryServiceProvider;
import sonia.scm.repository.spi.RepositoryServiceResolver;
import sonia.scm.security.ScmSecurityException;
@@ -71,6 +66,8 @@ import sonia.scm.security.ScmSecurityException;
//~--- JDK imports ------------------------------------------------------------
import java.util.Set;
+import sonia.scm.event.ScmEventBus;
+import sonia.scm.repository.ClearRepositoryCacheEvent;
/**
* The {@link RepositoryServiceFactory} is the entrypoint of the repository api.
@@ -285,38 +282,44 @@ public final class RepositoryServiceFactory
//~--- inner classes --------------------------------------------------------
/**
- * TODO find a more elegant way
- *
- *
- * @version Enter version here..., 12/06/16
- * @author Enter your name here...
+ * Hook and listener to clear all relevant repository caches.
*/
private static class CacheClearHook
{
+
+ private final Set> caches = Sets.newHashSet();
/**
- * Constructs ...
+ * Constructs a new instance and collect all repository relevant
+ * caches from the {@link CacheManager}.
*
- *
- * @param cacheManager
+ * @param cacheManager cache manager
*/
public CacheClearHook(CacheManager cacheManager)
{
- this.blameCache = cacheManager.getCache(BlameCommandBuilder.CACHE_NAME);
- this.browseCache = cacheManager.getCache(BrowseCommandBuilder.CACHE_NAME);
- this.logCache = cacheManager.getCache(LogCommandBuilder.CACHE_NAME);
- this.tagsCache = cacheManager.getCache(TagsCommandBuilder.CACHE_NAME);
- this.branchesCache =
- cacheManager.getCache(BranchesCommandBuilder.CACHE_NAME);
+ this.caches.add(cacheManager.getCache(BlameCommandBuilder.CACHE_NAME));
+ this.caches.add(cacheManager.getCache(BrowseCommandBuilder.CACHE_NAME));
+ this.caches.add(cacheManager.getCache(LogCommandBuilder.CACHE_NAME));
+ this.caches.add(cacheManager.getCache(TagsCommandBuilder.CACHE_NAME));
+ this.caches.add(cacheManager.getCache(BranchesCommandBuilder.CACHE_NAME));
}
//~--- methods ------------------------------------------------------------
/**
- * Method description
+ * Clear caches on explicit repository cache clear event.
+ *
+ * @param event clear event
+ */
+ @Subscribe
+ public void onEvent(ClearRepositoryCacheEvent event) {
+ clearCaches(event.getRepository().getId());
+ }
+
+ /**
+ * Clear caches on repository push.
*
- *
- * @param event
+ * @param event hook event
*/
@Subscribe(referenceType = ReferenceType.STRONG)
public void onEvent(PostReceiveRepositoryHookEvent event)
@@ -332,11 +335,10 @@ public final class RepositoryServiceFactory
}
/**
- * Method description
+ * Clear caches on repository delete event.
*
- *
- * @param repository
- * @param event
+ * @param repository changed repository
+ * @param event repository event
*/
@Subscribe(referenceType = ReferenceType.STRONG)
public void onEvent(RepositoryEvent event)
@@ -346,13 +348,7 @@ public final class RepositoryServiceFactory
clearCaches(event.getItem().getId());
}
}
-
- /**
- * Method description
- *
- *
- * @param repositoryId
- */
+
@SuppressWarnings("unchecked")
private void clearCaches(final String repositoryId)
{
@@ -361,32 +357,11 @@ public final class RepositoryServiceFactory
logger.debug("clear caches for repository id {}", repositoryId);
}
- RepositoryCacheKeyPredicate filter =
- new RepositoryCacheKeyPredicate(repositoryId);
-
- blameCache.removeAll(filter);
- browseCache.removeAll(filter);
- logCache.removeAll(filter);
- tagsCache.removeAll(filter);
- branchesCache.removeAll(filter);
+ RepositoryCacheKeyPredicate predicate = new RepositoryCacheKeyPredicate(repositoryId);
+ caches.forEach((cache) -> {
+ cache.removeAll(predicate);
+ });
}
-
- //~--- fields -------------------------------------------------------------
-
- /** blame cache */
- private final Cache blameCache;
-
- /** branches cache */
- private final Cache branchesCache;
-
- /** browse cache */
- private final Cache browseCache;
-
- /** log cache */
- private final Cache logCache;
-
- /** tags cache */
- private final Cache tagsCache;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java
index 0bc91679b7..2419cd45ac 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java
@@ -33,16 +33,20 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
+import sonia.scm.repository.api.HookBranchProvider;
import sonia.scm.repository.api.HookException;
import sonia.scm.repository.api.HookFeature;
import sonia.scm.repository.api.HookFeatureIsNotSupportedException;
import sonia.scm.repository.api.HookMessageProvider;
+import sonia.scm.repository.api.HookContext;
+import sonia.scm.repository.api.HookTagProvider;
//~--- JDK imports ------------------------------------------------------------
import java.util.Set;
/**
+ * Repository type specific provider for {@link HookContext}.
*
* @author Sebastian Sdorra
* @since 1.33
@@ -51,10 +55,10 @@ public abstract class HookContextProvider
{
/**
- * Method description
- *
- *
- * @return
+ * Return the provider specific {@link HookMessageProvider} or throws a {@link HookFeatureIsNotSupportedException}.
+ * The method will throw a {@link HookException} if the client is already disconnected.
+ *
+ * @return provider specific {@link HookMessageProvider}
*/
public final HookMessageProvider getMessageProvider()
{
@@ -70,7 +74,7 @@ public abstract class HookContextProvider
//~--- methods --------------------------------------------------------------
/**
- * Method description
+ * Mark client connection as disconnected.
*
*/
final void handleClientDisconnect()
@@ -81,32 +85,52 @@ public abstract class HookContextProvider
//~--- get methods ----------------------------------------------------------
/**
- * Method description
+ * Returns a set of supported hook features of the client.
*
- *
- * @return
+ * @return supported features
*/
public abstract Set getSupportedFeatures();
/**
- * Method description
- *
- *
- * @return
+ * Return the provider specific {@link HookBranchProvider} or throws a {@link HookFeatureIsNotSupportedException}.
+ *
+ * @return provider specific {@link HookBranchProvider}
+ *
+ * @since 1.45
+ */
+ public HookBranchProvider getBranchProvider()
+ {
+ throw new HookFeatureIsNotSupportedException(HookFeature.BRANCH_PROVIDER);
+ }
+
+ /**
+ * Return the provider specific {@link HookTagProvider} or throws a {@link HookFeatureIsNotSupportedException}.
+ *
+ * @return provider specific {@link HookTagProvider}
+ *
+ * @since 1.50
+ */
+ public HookTagProvider getTagProvider()
+ {
+ throw new HookFeatureIsNotSupportedException(HookFeature.TAG_PROVIDER);
+ }
+
+ /**
+ * Return the provider specific {@link HookChangesetProvider} or throws a {@link HookFeatureIsNotSupportedException}.
+ *
+ * @return provider specific {@link HookChangesetProvider}
*/
public HookChangesetProvider getChangesetProvider()
{
- throw new HookFeatureIsNotSupportedException(
- HookFeature.CHANGESET_PROVIDER);
+ throw new HookFeatureIsNotSupportedException(HookFeature.CHANGESET_PROVIDER);
}
//~--- methods --------------------------------------------------------------
/**
- * Method description
- *
- *
- * @return
+ * Creates a new provider specific {@link HookMessageProvider} or throws a {@link HookFeatureIsNotSupportedException}.
+ *
+ * @return provider specific {@link HookChangesetProvider}
*/
protected HookMessageProvider createMessageProvider()
{
@@ -115,6 +139,5 @@ public abstract class HookContextProvider
//~--- fields ---------------------------------------------------------------
- /** Field description */
private boolean clientDisconnected = false;
}
diff --git a/scm-core/src/main/java/sonia/scm/schedule/Scheduler.java b/scm-core/src/main/java/sonia/scm/schedule/Scheduler.java
new file mode 100644
index 0000000000..dfad4baeed
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/schedule/Scheduler.java
@@ -0,0 +1,65 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.schedule;
+
+import java.io.Closeable;
+
+/**
+ * Scheduler is able to run tasks on the future in a background thread. Task can be scheduled with cron like expression.
+ * Note: Task are always executed in an administrative context.
+ * @author Sebastian Sdorra
+ * @since 1.47
+ */
+public interface Scheduler extends Closeable {
+
+ /**
+ * Schedule a new task for future execution.
+ *
+ * @param expression cron expression
+ * @param runnable action
+ *
+ * @return cancelable task
+ */
+ public Task schedule(String expression, Runnable runnable);
+
+ /**
+ * Schedule a new task for future execution. The method will create a new instance of the runnable for every
+ * execution. The runnable can use injection.
+ *
+ * @param expression cron expression
+ * @param runnable action class
+ *
+ * @return cancelable task
+ */
+ public Task schedule(String expression, Class extends Runnable> runnable);
+
+}
diff --git a/scm-core/src/main/java/sonia/scm/schedule/Task.java b/scm-core/src/main/java/sonia/scm/schedule/Task.java
new file mode 100644
index 0000000000..dc601bcb82
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/schedule/Task.java
@@ -0,0 +1,47 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.schedule;
+
+/**
+ * Tasks are executed in the future and can be running more than once. A task execution can be canceled.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.47
+ */
+public interface Task {
+
+ /**
+ * Cancel task execution.
+ */
+ public void cancel();
+
+}
diff --git a/scm-core/src/main/java/sonia/scm/security/CipherHandler.java b/scm-core/src/main/java/sonia/scm/security/CipherHandler.java
index 8463f4a4e3..a27c4b4e28 100644
--- a/scm-core/src/main/java/sonia/scm/security/CipherHandler.java
+++ b/scm-core/src/main/java/sonia/scm/security/CipherHandler.java
@@ -35,7 +35,8 @@ package sonia.scm.security;
/**
- *
+ * Encrypts and decrypts string values.
+ *
* @author Sebastian Sdorra
* @since 1.7
*/
@@ -43,22 +44,20 @@ public interface CipherHandler
{
/**
- * Method description
+ * Decrypts the given value.
*
+ * @param value encrypted value
*
- * @param value
- *
- * @return
+ * @return decrypted value
*/
public String decode(String value);
/**
- * Method description
+ * Encrypts the given value.
*
+ * @param value plain text value to encrypt.
*
- * @param value
- *
- * @return
+ * @return encrypted value
*/
public String encode(String value);
}
diff --git a/scm-core/src/main/java/sonia/scm/security/DefaultCipherHandler.java b/scm-core/src/main/java/sonia/scm/security/DefaultCipherHandler.java
index 4b82563687..ddb8a699a7 100644
--- a/scm-core/src/main/java/sonia/scm/security/DefaultCipherHandler.java
+++ b/scm-core/src/main/java/sonia/scm/security/DefaultCipherHandler.java
@@ -53,7 +53,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -66,32 +66,32 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
- *
+ * Default implementation of the {@link CipherHandler}, which uses AES for
+ * encryption and decryption.
+ *
* @author Sebastian Sdorra
* @since 1.7
*/
-public class DefaultCipherHandler implements CipherHandler
-{
+public class DefaultCipherHandler implements CipherHandler {
- /** Field description */
+ /** used cipher type */
public static final String CIPHER_TYPE = "AES/CTR/PKCS5PADDING";
- /** Field description */
+ /** digest type for key generation */
public static final String DIGEST_TYPE = "SHA-512";
- /** Field description */
+ /** string encoding */
public static final String ENCODING = "UTF-8";
- /** Field description */
+ /** default key length */
public static final int KEY_LENGTH = 16;
- /** Field description */
+ /** default salt length */
public static final int SALT_LENGTH = 16;
- /** Field description */
- private static final String CIPHERKEY_FILENAME = ".cipherkey";
+ @VisibleForTesting
+ static final String CIPHERKEY_FILENAME = ".cipherkey";
- /** Field description */
private static final char[] KEY_BASE = new char[]
{
'1', '4', '7', '3', 'F', '2', '1', 'E', '-', 'C', '4', 'C', '4', '-', '4',
@@ -99,96 +99,72 @@ public class DefaultCipherHandler implements CipherHandler
'E', 'C', '7', '7', '2', 'E'
};
- /** Field description */
private static final String KEY_TYPE = "AES";
/** the logger for DefaultCipherHandler */
- private static final Logger logger =
- LoggerFactory.getLogger(DefaultCipherHandler.class);
-
- //~--- constructors ---------------------------------------------------------
-
+ private static final Logger logger = LoggerFactory.getLogger(DefaultCipherHandler.class);
+
+ private final SecureRandom random = new SecureRandom();
+
+ private final char[] key;
+
/**
* Constructs a new DefaultCipherHandler. Note this constructor is only for
* unit tests.
*
- *
- * @param key
+ * @param key default encryption key
*
* @since 1.38
*/
@VisibleForTesting
- protected DefaultCipherHandler(String key)
- {
+ protected DefaultCipherHandler(String key) {
this.key = key.toCharArray();
}
/**
- * Constructs ...
- *
- *
- * @param context
- * @param keyGenerator
- *
+ * Constructs a new instance and reads the default key from the scm home directory,
+ * if the key file does not exists it will be generated with the {@link KeyGenerator}.
*
+ * @param context SCM-Manager context provider
+ * @param keyGenerator key generator for default key generation
*/
- public DefaultCipherHandler(SCMContextProvider context,
- KeyGenerator keyGenerator)
- {
+ public DefaultCipherHandler(SCMContextProvider context, KeyGenerator keyGenerator) {
File configDirectory = new File(context.getBaseDirectory(), "config");
IOUtil.mkdirs(configDirectory);
- cipherKeyFile = new File(configDirectory, CIPHERKEY_FILENAME);
+ File cipherKeyFile = new File(configDirectory, CIPHERKEY_FILENAME);
- try
- {
- if (cipherKeyFile.exists())
- {
- loadKey();
- }
- else
- {
+ try {
+ if (cipherKeyFile.exists()) {
+ key = loadKey(cipherKeyFile);
+ } else {
key = keyGenerator.createKey().toCharArray();
- storeKey();
+ storeKey(cipherKeyFile);
}
- }
- catch (IOException ex)
- {
+ } catch (IOException ex) {
throw new CipherException("could not create CipherHandler", ex);
}
}
//~--- methods --------------------------------------------------------------
- /**
- * Method description
- *
- *
- * @param value
- *
- * @return
- */
@Override
- public String decode(String value)
- {
+ public String decode(String value) {
return decode(key, value);
}
/**
- * Method description
+ * Decodes the given value with the provided key.
*
+ * @param plainKey key which is used for decoding
+ * @param value encrypted value
*
- * @param plainKey
- * @param value
- *
- * @return
+ * @return decrypted value
*/
- public String decode(char[] plainKey, String value)
- {
+ public String decode(char[] plainKey, String value) {
String result = null;
- try
- {
+ try {
byte[] encodedInput = Base64.decode(value);
byte[] salt = new byte[SALT_LENGTH];
byte[] encoded = new byte[encodedInput.length - SALT_LENGTH];
@@ -206,52 +182,35 @@ public class DefaultCipherHandler implements CipherHandler
byte[] decoded = cipher.doFinal(encoded);
result = new String(decoded, ENCODING);
- }
- catch (Exception ex)
- {
- logger.error("could not decode string", ex);
-
- throw new CipherException(ex);
+ } catch (IOException | GeneralSecurityException ex) {
+ throw new CipherException("could not decode string", ex);
}
return result;
}
- /**
- * Method description
- *
- *
- * @param value
- *
- * @return
- */
@Override
- public String encode(String value)
- {
+ public String encode(String value) {
return encode(key, value);
}
/**
- * Method description
+ * Encrypts the given value with the provided key.
*
+ * @param plainKey key which is used for encoding
+ * @param value plain text value to encrypt
*
- * @param plainKey
- * @param value
- *
- * @return
+ * @return encrypted value
*/
- public String encode(char[] plainKey, String value)
- {
+ public String encode(char[] plainKey, String value) {
String res = null;
-
- try
- {
+ try {
byte[] salt = new byte[SALT_LENGTH];
random.nextBytes(salt);
IvParameterSpec iv = new IvParameterSpec(salt);
- SecretKey secretKey = buildSecretKey(key);
+ SecretKey secretKey = buildSecretKey(plainKey);
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_TYPE);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, iv);
@@ -264,31 +223,14 @@ public class DefaultCipherHandler implements CipherHandler
System.arraycopy(encodedInput, 0, result, SALT_LENGTH,
result.length - SALT_LENGTH);
res = new String(Base64.encode(result), ENCODING);
- }
- catch (Exception ex)
- {
- logger.error("could not encode string", ex);
-
- throw new CipherException(ex);
+ } catch (IOException | GeneralSecurityException ex) {
+ throw new CipherException("could not encode string", ex);
}
return res;
}
- /**
- * Method description
- *
- *
- * @param plainKey
- *
- * @return
- *
- * @throws NoSuchAlgorithmException
- * @throws UnsupportedEncodingException
- */
- private SecretKey buildSecretKey(char[] plainKey)
- throws UnsupportedEncodingException, NoSuchAlgorithmException
- {
+ private SecretKey buildSecretKey(char[] plainKey) throws IOException, NoSuchAlgorithmException {
byte[] raw = new String(plainKey).getBytes(ENCODING);
MessageDigest digest = MessageDigest.getInstance(DIGEST_TYPE);
@@ -298,60 +240,18 @@ public class DefaultCipherHandler implements CipherHandler
return new SecretKeySpec(raw, KEY_TYPE);
}
- /**
- * Method description
- *
- *
- * @throws IOException
- */
- private void loadKey() throws IOException
- {
- BufferedReader reader = null;
-
- try
- {
- reader = new BufferedReader(new FileReader(cipherKeyFile));
-
+ private char[] loadKey(File cipherKeyFile) throws IOException {
+ try (BufferedReader reader = new BufferedReader(new FileReader(cipherKeyFile))) {
String line = reader.readLine();
- key = decode(KEY_BASE, line).toCharArray();
- }
- finally
- {
- IOUtil.close(reader);
+ return decode(KEY_BASE, line).toCharArray();
}
}
- /**
- * Method description
- *
- *
- * @throws FileNotFoundException
- */
- private void storeKey() throws FileNotFoundException
- {
+ private void storeKey(File cipherKeyFile) throws FileNotFoundException {
String storeKey = encode(KEY_BASE, new String(key));
- PrintWriter output = null;
-
- try
- {
- output = new PrintWriter(cipherKeyFile);
+ try (PrintWriter output = new PrintWriter(cipherKeyFile)) {
output.write(storeKey);
}
- finally
- {
- IOUtil.close(output);
- }
}
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private File cipherKeyFile;
-
- /** Field description */
- private char[] key = null;
-
- /** Field description */
- private SecureRandom random = new SecureRandom();
}
diff --git a/scm-core/src/main/java/sonia/scm/user/AbstractUserManager.java b/scm-core/src/main/java/sonia/scm/user/AbstractUserManager.java
index 80187e87a9..e659ff08e8 100644
--- a/scm-core/src/main/java/sonia/scm/user/AbstractUserManager.java
+++ b/scm-core/src/main/java/sonia/scm/user/AbstractUserManager.java
@@ -56,17 +56,28 @@ public abstract class AbstractUserManager implements UserManager
*/
protected void fireEvent(HandlerEventType event, User user, User oldUser)
{
- ScmEventBus.getInstance().post(new UserEvent(event, user, oldUser));
+ fireEvent(new UserModificationEvent(event, user, oldUser));
}
/**
- * Send a {@link UserEvent} to the {@link ScmEventBus}.
+ * Creates a new {@link UserEvent} and calls {@link #fireEvent(sonia.scm.user.UserEvent)}.
*
* @param user user that has changed
* @param event type of change event
*/
protected void fireEvent(HandlerEventType event, User user)
{
- ScmEventBus.getInstance().post(new UserEvent(event, user));
+ fireEvent(new UserEvent(event, user));
+ }
+
+ /**
+ * Send a {@link UserEvent} to the {@link ScmEventBus}.
+ *
+ * @param event user event
+ * @since 1.48
+ */
+ protected void fireEvent(UserEvent event)
+ {
+ ScmEventBus.getInstance().post(event);
}
}
diff --git a/scm-core/src/main/java/sonia/scm/user/UserEvent.java b/scm-core/src/main/java/sonia/scm/user/UserEvent.java
index 18e242d525..abe24cbb59 100644
--- a/scm-core/src/main/java/sonia/scm/user/UserEvent.java
+++ b/scm-core/src/main/java/sonia/scm/user/UserEvent.java
@@ -46,7 +46,7 @@ import sonia.scm.event.Event;
* @since 1.23
*/
@Event
-public final class UserEvent extends AbstractHandlerEvent
+public class UserEvent extends AbstractHandlerEvent
{
/**
diff --git a/scm-core/src/main/java/sonia/scm/user/UserModificationEvent.java b/scm-core/src/main/java/sonia/scm/user/UserModificationEvent.java
new file mode 100644
index 0000000000..f3c7ec11ba
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/user/UserModificationEvent.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.user;
+
+import sonia.scm.HandlerEventType;
+import sonia.scm.ModificationHandlerEvent;
+import sonia.scm.event.Event;
+
+/**
+ * Event which is fired whenever a user is modified.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.48
+ */
+@Event
+public class UserModificationEvent extends UserEvent implements ModificationHandlerEvent
+{
+
+ private final User itemBeforeModification;
+
+ /**
+ * Constructs a new {@link UserModificationEvent}.
+ *
+ * @param eventType type of event
+ * @param item changed user
+ * @param itemBeforeModification changed user before it was modified
+ */
+ public UserModificationEvent(HandlerEventType eventType, User item, User itemBeforeModification)
+ {
+ super(eventType, item);
+ this.itemBeforeModification = itemBeforeModification;
+ }
+
+ @Override
+ public User getItemBeforeModification()
+ {
+ return itemBeforeModification;
+ }
+
+}
diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
index daa0f2c245..89d1646d5e 100644
--- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
+++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java
@@ -35,7 +35,9 @@ package sonia.scm.util;
//~--- non-JDK imports --------------------------------------------------------
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
+import com.google.common.base.Objects;
import com.google.common.base.Strings;
import org.slf4j.Logger;
@@ -96,6 +98,12 @@ public final class HttpUtil
* @since 2.0.0
*/
public static final String HEADER_AUTHORIZATION = "Authorization";
+
+ /**
+ * content-length header
+ * @since 1.46
+ */
+ public static final String HEADER_CONTENT_LENGTH = "Content-Length";
/**
* location header
@@ -115,6 +123,24 @@ public final class HttpUtil
/** authentication header */
public static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+ /**
+ * The original host requested by the client in the Host HTTP request header.
+ * @since 1.47
+ */
+ public static final String HEADER_X_FORWARDED_HOST = "X-Forwarded-Host";
+
+ /**
+ * The original port requested by the client.
+ * @since 1.47
+ */
+ public static final String HEADER_X_FORWARDED_PORT = "X-Forwarded-Port";
+
+ /**
+ * The original protocol (http or https) requested by the client.
+ * @since 1.47
+ */
+ public static final String HEADER_X_FORWARDED_PROTO = "X-Forwarded-Proto";
+
/**
* Default http port
* @since 1.5
@@ -315,8 +341,26 @@ public final class HttpUtil
}
/**
- * Url decode.
+ * Creates the value for the content-disposition attachment header. The method
+ * creates the filename as specified in rfc6266.
*
+ * @param name attachment name
+ * @see rfc6266 section 5
+ * @return value of content-disposition header
+ * @since 1.46
+ */
+ public static String createContentDispositionAttachmentHeader(String name)
+ {
+ StringBuilder buffer = new StringBuilder("attachment; ");
+
+ buffer.append("filename=\"").append(name).append("\"; ");
+ buffer.append("filename*=utf-8''").append(encode(name));
+
+ return buffer.toString();
+ }
+
+ /**
+ * Url decode.
*
* @param value value to decode
*
@@ -578,21 +622,31 @@ public final class HttpUtil
//~--- get methods ----------------------------------------------------------
/**
- * Returns an absolute url with context path.
+ * Returns an absolute url with context path. The method creates the url from
+ * forwarding request headers, if they are available.
*
*
* @param request http client request
* @param pathSegments
*
* @return absolute url with context path
+ *
+ * @see Issue 748
* @since 1.16
*/
public static String getCompleteUrl(HttpServletRequest request,
String... pathSegments)
{
- String baseUrl =
- request.getRequestURL().toString().replace(request.getRequestURI(),
- Util.EMPTY_STRING).concat(request.getContextPath());
+ String baseUrl;
+
+ if (isForwarded(request))
+ {
+ baseUrl = createForwardedBaseUrl(request);
+ }
+ else
+ {
+ baseUrl = createBaseUrl(request);
+ }
if (Util.isNotEmpty(pathSegments))
{
@@ -622,6 +676,24 @@ public final class HttpUtil
return append(configuration.getBaseUrl(), path);
}
+ /**
+ * Method description
+ *
+ *
+ * @param request
+ * @param header
+ * @param defaultValue
+ *
+ * @return
+ */
+ public static String getHeader(HttpServletRequest request, String header,
+ String defaultValue)
+ {
+ String value = request.getHeader(header);
+
+ return Objects.firstNonNull(value, defaultValue);
+ }
+
/**
* Returns the port of the url parameter.
*
@@ -776,6 +848,21 @@ public final class HttpUtil
return "chunked".equals(request.getHeader("Transfer-Encoding"));
}
+ /**
+ * Returns {@code true} if the request is forwarded by a reverse proxy. The
+ * method uses the X-Forwarded-Host header to identify a forwarded request.
+ *
+ * @param request servlet request
+ *
+ * @return {@code true} if the request is forwarded
+ *
+ * @since 1.47
+ */
+ public static boolean isForwarded(HttpServletRequest request)
+ {
+ return !Strings.isNullOrEmpty(request.getHeader(HEADER_X_FORWARDED_HOST));
+ }
+
/**
* Returns true if the http request is send by the scm-manager web interface.
*
@@ -790,4 +877,74 @@ public final class HttpUtil
return SCM_CLIENT_WUI.equalsIgnoreCase(
request.getHeader(HEADER_SCM_CLIENT));
}
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Creates base url for request url.
+ *
+ * @param request http servlet request
+ *
+ * @return base url from request
+ *
+ * @since 1.47
+ */
+ @VisibleForTesting
+ static String createBaseUrl(HttpServletRequest request)
+ {
+ return request.getRequestURL().toString().replace(request.getRequestURI(),
+ Util.EMPTY_STRING).concat(request.getContextPath());
+ }
+
+ /**
+ * Creates base url from forwarding request headers.
+ *
+ * @param request http servlet request
+ *
+ * @return base url from forward headers
+ *
+ * @since 1.47
+ */
+ @VisibleForTesting
+ static String createForwardedBaseUrl(HttpServletRequest request)
+ {
+ String proto = getHeader(request, HEADER_X_FORWARDED_PROTO,
+ request.getScheme());
+ String host;
+ String fhost = getHeader(request, HEADER_X_FORWARDED_HOST,
+ request.getScheme());
+ String port = request.getHeader(HEADER_X_FORWARDED_PORT);
+ int s = fhost.indexOf(SEPARATOR_PORT);
+
+ if (s > 0)
+ {
+ host = fhost.substring(0, s);
+
+ if (Strings.isNullOrEmpty(port))
+ {
+ port = fhost.substring(s + 1);
+ }
+ }
+ else
+ {
+ host = fhost;
+ }
+
+ StringBuilder buffer = new StringBuilder(proto);
+
+ buffer.append(SEPARATOR_SCHEME).append(host).append(SEPARATOR_PORT);
+
+ if (Strings.isNullOrEmpty(port))
+ {
+ buffer.append(String.valueOf(request.getServerPort()));
+ }
+ else
+ {
+ buffer.append(port);
+ }
+
+ buffer.append(request.getContextPath());
+
+ return buffer.toString();
+ }
}
diff --git a/scm-core/src/main/java/sonia/scm/util/UrlBuilder.java b/scm-core/src/main/java/sonia/scm/util/UrlBuilder.java
index cd39723ec1..c844f65f11 100644
--- a/scm-core/src/main/java/sonia/scm/util/UrlBuilder.java
+++ b/scm-core/src/main/java/sonia/scm/util/UrlBuilder.java
@@ -35,6 +35,7 @@ package sonia.scm.util;
//~--- JDK imports ------------------------------------------------------------
+import com.google.common.net.UrlEscapers;
import java.net.MalformedURLException;
import java.net.URL;
@@ -138,8 +139,11 @@ public class UrlBuilder
{
if (Util.isNotEmpty(name) && Util.isNotEmpty(value))
{
- url = new StringBuilder(url).append(separator).append(name).append(
- HttpUtil.SEPARATOR_PARAMETER_VALUE).append(value).toString();
+ url = new StringBuilder(url)
+ .append(separator).append(name)
+ .append(HttpUtil.SEPARATOR_PARAMETER_VALUE)
+ .append(UrlEscapers.urlFragmentEscaper().escape(value))
+ .toString();
separator = HttpUtil.SEPARATOR_PARAMETER;
parameterAdded = true;
}
diff --git a/scm-core/src/main/java/sonia/scm/web/UserAgent.java b/scm-core/src/main/java/sonia/scm/web/UserAgent.java
new file mode 100644
index 0000000000..7efd1bd1c2
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/web/UserAgent.java
@@ -0,0 +1,257 @@
+/**
+* Copyright (c) 2010, Sebastian Sdorra
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright notice,
+* this list of conditions and the following disclaimer in the documentation
+* and/or other materials provided with the distribution.
+* 3. Neither the name of SCM-Manager; nor the names of its
+* contributors may be used to endorse or promote products derived from this
+* software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+* http://bitbucket.org/sdorra/scm-manager
+*
+*/
+
+
+
+package sonia.scm.web;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Objects;
+
+import static com.google.common.base.Preconditions.*;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.nio.charset.Charset;
+
+/**
+ * The software agent that is acting on behalf of a user. The user agent
+ * represents a browser or one of the repository client (svn, git or hg).
+ *
+ * @author Sebastian Sdorra
+ * @since 1.45
+ */
+public final class UserAgent
+{
+
+ /**
+ * Constructs a new user agent
+ *
+ *
+ * @param name
+ * @param browser
+ * @param basicAuthenticationCharset
+ */
+ private UserAgent(String name, boolean browser,
+ Charset basicAuthenticationCharset)
+ {
+ this.name = checkNotNull(name);
+ this.browser = browser;
+ this.basicAuthenticationCharset = checkNotNull(basicAuthenticationCharset);
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Returns the {@link Builder} for the UserAgent.
+ *
+ *
+ * @param name name of the UserAgent
+ *
+ * @return builder for UserAgent
+ */
+ public static Builder builder(String name)
+ {
+ return new Builder(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+
+ final UserAgent other = (UserAgent) obj;
+
+ return Objects.equal(name, other.name)
+ && Objects.equal(browser, other.browser)
+ && Objects.equal(basicAuthenticationCharset, basicAuthenticationCharset);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode()
+ {
+ return Objects.hashCode(name, browser, basicAuthenticationCharset);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ //J-
+ return Objects.toStringHelper(this)
+ .add("name", name)
+ .add("browser", browser)
+ .add("basicAuthenticationCharset", basicAuthenticationCharset)
+ .toString();
+ //J+
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Returns the {@link Charset}, which is used to decode the basic
+ * authentication header.
+ *
+ * @return {@link Charset} for basic authentication
+ */
+ public Charset getBasicAuthenticationCharset()
+ {
+ return basicAuthenticationCharset;
+ }
+
+ /**
+ * Returns the name of UserAgent.
+ *
+ *
+ * @return name of UserAgent
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Returns {@code true} if UserAgent is a browser.
+ *
+ *
+ * @return {@code true} if UserAgent is a browser
+ */
+ public boolean isBrowser()
+ {
+ return browser;
+ }
+
+ //~--- inner classes --------------------------------------------------------
+
+ /**
+ * Builder class for {@link UserAgent}.
+ */
+ public static class Builder
+ {
+
+ /**
+ * Constructs a new UserAgent builder.
+ *
+ *
+ * @param name name of the UserAgent
+ */
+ public Builder(String name)
+ {
+ this.name = name;
+ }
+
+ //~--- methods ------------------------------------------------------------
+
+ /**
+ * Sets {@link Charset} which is used to decode the basic authentication.
+ *
+ *
+ * @param basicAuthenticationCharset charset for basic authentication
+ *
+ * @return {@code this}
+ */
+ public Builder basicAuthenticationCharset(
+ Charset basicAuthenticationCharset)
+ {
+ this.basicAuthenticationCharset =
+ checkNotNull(basicAuthenticationCharset);
+
+ return this;
+ }
+
+ /**
+ * Set to {@code true} if the {@link UserAgent} is a browser.
+ *
+ *
+ * @param browser {@code true} for a browser
+ *
+ * @return {@code this}
+ */
+ public Builder browser(boolean browser)
+ {
+ this.browser = browser;
+
+ return this;
+ }
+
+ /**
+ * Builds the {@link UserAgent}.
+ *
+ *
+ * @return new {@link UserAgent}
+ */
+ public UserAgent build()
+ {
+ return new UserAgent(name, browser, basicAuthenticationCharset);
+ }
+
+ //~--- fields -------------------------------------------------------------
+
+ /** name of UserAgent */
+ private final String name;
+
+ /** indicator for browsers */
+ private boolean browser = true;
+
+ /** basic authentication charset */
+ private Charset basicAuthenticationCharset = Charsets.ISO_8859_1;
+ }
+
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** basic authentication charset */
+ private final Charset basicAuthenticationCharset;
+
+ /** indicator for browsers */
+ private final boolean browser;
+
+ /** name of UserAgent */
+ private final String name;
+}
diff --git a/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java b/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java
new file mode 100644
index 0000000000..d1c0353e5f
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java
@@ -0,0 +1,157 @@
+/**
+* Copyright (c) 2010, Sebastian Sdorra
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright notice,
+* this list of conditions and the following disclaimer in the documentation
+* and/or other materials provided with the distribution.
+* 3. Neither the name of SCM-Manager; nor the names of its
+* contributors may be used to endorse or promote products derived from this
+* software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+* http://bitbucket.org/sdorra/scm-manager
+*
+*/
+
+
+
+package sonia.scm.web;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import sonia.scm.cache.Cache;
+import sonia.scm.cache.CacheManager;
+import sonia.scm.util.HttpUtil;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.util.Locale;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Parser for User-Agent header. The UserAgentParser parses the User-Agent
+ * header and returns a {@link UserAgent} object.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.45
+ */
+@Singleton
+public final class UserAgentParser
+{
+
+ /** name of the cache */
+ @VisibleForTesting
+ static final String CACHE_NAME = "sonia.scm.user-agent";
+
+ /** unknown UserAgent */
+ @VisibleForTesting
+ static final UserAgent UNKNOWN = UserAgent.builder("UNKNOWN").build();
+
+ /** logger */
+ private static final Logger logger =
+ LoggerFactory.getLogger(UserAgentParser.class);
+
+ //~--- constructors ---------------------------------------------------------
+
+ /**
+ * Constructs a new UserAgentParser.
+ *
+ *
+ * @param providers set of providers
+ * @param cacheManager cache manager
+ */
+ @Inject
+ public UserAgentParser(Set providers,
+ CacheManager cacheManager)
+ {
+ this.providers = providers;
+ this.cache = cacheManager.getCache(CACHE_NAME);
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Extracts the User-Agent header and returns an {@link UserAgent} object.
+ *
+ *
+ * @param request http request
+ *
+ * @return {@link UserAgent} object
+ */
+ public UserAgent parse(HttpServletRequest request)
+ {
+ return parse(request.getHeader(HttpUtil.HEADER_USERAGENT));
+ }
+
+ /**
+ * Parses the User-Agent header and returns a {@link UserAgent} object.
+ *
+ *
+ * @param userAgent User-Agent header
+ *
+ * @return {@link UserAgent} object
+ */
+ public UserAgent parse(String userAgent)
+ {
+ String uas = Strings.nullToEmpty(userAgent).toLowerCase(Locale.ENGLISH);
+ UserAgent ua = cache.get(uas);
+
+ if (ua == null)
+ {
+ for (UserAgentProvider provider : providers)
+ {
+ ua = provider.parseUserAgent(uas);
+
+ if (ua != null)
+ {
+ break;
+ }
+ }
+
+ if (ua == null)
+ {
+ ua = UNKNOWN;
+ }
+
+ cache.put(uas, ua);
+ }
+
+ logger.trace("return user-agent {} for {}", ua, userAgent);
+
+ return ua;
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** cache for parsed UserAgents */
+ private final Cache cache;
+
+ /** set of providers */
+ private final Set providers;
+}
diff --git a/scm-core/src/main/java/sonia/scm/web/UserAgentProvider.java b/scm-core/src/main/java/sonia/scm/web/UserAgentProvider.java
new file mode 100644
index 0000000000..2eb58a8a36
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/web/UserAgentProvider.java
@@ -0,0 +1,58 @@
+/**
+* Copyright (c) 2010, Sebastian Sdorra
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright notice,
+* this list of conditions and the following disclaimer in the documentation
+* and/or other materials provided with the distribution.
+* 3. Neither the name of SCM-Manager; nor the names of its
+* contributors may be used to endorse or promote products derived from this
+* software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+* http://bitbucket.org/sdorra/scm-manager
+*
+*/
+
+
+
+package sonia.scm.web;
+
+import sonia.scm.plugin.ExtensionPoint;
+
+/**
+ * Provider to parse User-Agent header and returns an {@link UserAgent} object.
+ * The {@link UserAgentProvider} is used by the {@link UserAgentParser}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.45
+ */
+@ExtensionPoint(multi = true)
+public interface UserAgentProvider
+{
+
+ /**
+ * Parses the User-Agent header and returns a {@link UserAgent} object.
+ *
+ *
+ * @param userAgentString User-Agent header
+ *
+ * @return {@link UserAgent} object
+ */
+ public UserAgent parseUserAgent(String userAgentString);
+}
diff --git a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java
index ec95d665a4..b88dda8a97 100644
--- a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java
+++ b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java
@@ -93,9 +93,7 @@ public class AuthenticationFilter extends HttpFilter
* @param tokenGenerators web token generators
*/
@Inject
- public AuthenticationFilter(ScmConfiguration configuration,
- Set tokenGenerators)
- {
+ public AuthenticationFilter(ScmConfiguration configuration, Set tokenGenerators) {
this.configuration = configuration;
this.tokenGenerators = tokenGenerators;
}
@@ -106,7 +104,6 @@ public class AuthenticationFilter extends HttpFilter
* Handles authentication, if a one of the {@link WebTokenGenerator} returns
* an {@link AuthenticationToken}.
*
- *
* @param request servlet request
* @param response servlet response
* @param chain filter chain
@@ -222,7 +219,6 @@ public class AuthenticationFilter extends HttpFilter
private AuthenticationToken createToken(HttpServletRequest request)
{
AuthenticationToken token = null;
-
for (WebTokenGenerator generator : tokenGenerators)
{
token = generator.createToken(request);
diff --git a/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java
index ad19971359..7517f4848c 100644
--- a/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java
+++ b/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java
@@ -144,33 +144,24 @@ public abstract class PermissionFilter extends HttpFilter
if (hasPermission(repository, writeRequest))
{
- if (logger.isTraceEnabled())
- {
- logger.trace("{} access to repository {} for user {} granted",
- getActionAsString(writeRequest), repository.getName(),
- getUserName(subject));
- }
+ logger.trace("{} access to repository {} for user {} granted",
+ getActionAsString(writeRequest), repository.getName(),
+ getUserName(subject));
chain.doFilter(request, response);
}
else
{
- if (logger.isInfoEnabled())
- {
- logger.info("{} access to repository {} for user {} denied",
- getActionAsString(writeRequest), repository.getName(),
- getUserName(subject));
- }
-
+ logger.info("{} access to repository {} for user {} denied",
+ getActionAsString(writeRequest), repository.getName(),
+ getUserName(subject));
+
sendAccessDenied(request, response, subject);
}
}
else
{
- if (logger.isDebugEnabled())
- {
- logger.debug("repository not found");
- }
+ logger.debug("repository not found");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
@@ -193,12 +184,7 @@ public abstract class PermissionFilter extends HttpFilter
}
catch (ScmSecurityException | AuthorizationException ex)
{
- if (logger.isWarnEnabled())
- {
- logger.warn("user {} has not enough permissions",
- subject.getPrincipal());
- }
-
+ logger.warn("user " + subject.getPrincipal() + " has not enough permissions", ex);
sendAccessDenied(request, response, subject);
}
diff --git a/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java b/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java
index 2fdbdd546a..2cd78ce807 100644
--- a/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java
+++ b/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java
@@ -45,7 +45,7 @@ import javax.servlet.http.HttpServletRequestWrapper;
public class SecurityHttpServletRequestWrapper extends HttpServletRequestWrapper
{
- /**
+ /**
* Constructs ...
*
*
@@ -61,9 +61,6 @@ public class SecurityHttpServletRequestWrapper extends HttpServletRequestWrapper
//~--- get methods ----------------------------------------------------------
- /**
- * {@inheritDoc}
- */
@Override
public String getRemoteUser()
{
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java
new file mode 100644
index 0000000000..59549c24dd
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AdvancedHttpClientTest {
+
+ @Mock(answer = Answers.CALLS_REAL_METHODS)
+ private AdvancedHttpClient client;
+
+ private static final String URL = "https://www.scm-manager.org";
+
+ @Test
+ public void testGet()
+ {
+ AdvancedHttpRequest request = client.get(URL);
+ assertEquals(URL, request.getUrl());
+ assertEquals(HttpMethod.GET, request.getMethod());
+ }
+
+ @Test
+ public void testDelete()
+ {
+ AdvancedHttpRequestWithBody request = client.delete(URL);
+ assertEquals(URL, request.getUrl());
+ assertEquals(HttpMethod.DELETE, request.getMethod());
+ }
+
+ @Test
+ public void testPut()
+ {
+ AdvancedHttpRequestWithBody request = client.put(URL);
+ assertEquals(URL, request.getUrl());
+ assertEquals(HttpMethod.PUT, request.getMethod());
+ }
+
+ @Test
+ public void testPost()
+ {
+ AdvancedHttpRequestWithBody request = client.post(URL);
+ assertEquals(URL, request.getUrl());
+ assertEquals(HttpMethod.POST, request.getMethod());
+ }
+
+ @Test
+ public void testOptions()
+ {
+ AdvancedHttpRequestWithBody request = client.options(URL);
+ assertEquals(URL, request.getUrl());
+ assertEquals(HttpMethod.OPTIONS, request.getMethod());
+ }
+
+ @Test
+ public void testHead()
+ {
+ AdvancedHttpRequest request = client.head(URL);
+ assertEquals(URL, request.getUrl());
+ assertEquals(HttpMethod.HEAD, request.getMethod());
+ }
+
+ @Test
+ public void testMethod()
+ {
+ AdvancedHttpRequestWithBody request = client.method("PROPFIND", URL);
+ assertEquals(URL, request.getUrl());
+ assertEquals("PROPFIND", request.getMethod());
+ }
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestTest.java
new file mode 100644
index 0000000000..5b2d675e97
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestTest.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AdvancedHttpRequestTest {
+
+ @Mock
+ private AdvancedHttpClient ahc;
+
+ @Test
+ public void testSelf()
+ {
+ AdvancedHttpRequest ahr = new AdvancedHttpRequest(ahc, HttpMethod.GET, "https://www.scm-manager.org");
+ assertEquals(AdvancedHttpRequest.class, ahr.self().getClass());
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java
new file mode 100644
index 0000000000..5a7c55a46d
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteSource;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+import static org.mockito.Mockito.*;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AdvancedHttpRequestWithBodyTest {
+
+ @Mock
+ private AdvancedHttpClient ahc;
+
+ @Mock
+ private ContentTransformer transformer;
+
+ private AdvancedHttpRequestWithBody request;
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Before
+ public void before(){
+ request = new AdvancedHttpRequestWithBody(ahc, HttpMethod.PUT, "https://www.scm-manager.org");
+ }
+
+ @Test
+ public void testContentLength()
+ {
+ request.contentLength(12l);
+ assertEquals("12", request.getHeaders().get("Content-Length").iterator().next());
+ }
+
+ @Test
+ public void testContentType(){
+ request.contentType("text/plain");
+ assertEquals("text/plain", request.getHeaders().get("Content-Type").iterator().next());
+ }
+
+ @Test
+ public void testFileContent() throws IOException{
+ File file = tempFolder.newFile();
+ request.fileContent(file);
+ assertThat(request.getContent(), instanceOf(FileContent.class));
+ }
+
+ @Test
+ public void testRawContent() throws IOException {
+ request.rawContent("test".getBytes(Charsets.UTF_8));
+ assertThat(request.getContent(), instanceOf(RawContent.class));
+ }
+
+ @Test
+ public void testRawContentWithByteSource() throws IOException {
+ ByteSource bs = ByteSource.wrap("test".getBytes(Charsets.UTF_8));
+ request.rawContent(bs);
+ assertThat(request.getContent(), instanceOf(ByteSourceContent.class));
+ }
+
+ @Test
+ public void testFormContent(){
+ FormContentBuilder builder = request.formContent();
+ assertNotNull(builder);
+ builder.build();
+ assertThat(request.getContent(), instanceOf(StringContent.class));
+ }
+
+ @Test
+ public void testStringContent(){
+ request.stringContent("test");
+ assertThat(request.getContent(), instanceOf(StringContent.class));
+ }
+
+ @Test
+ public void testStringContentWithCharset(){
+ request.stringContent("test", Charsets.UTF_8);
+ assertThat(request.getContent(), instanceOf(StringContent.class));
+ }
+
+ @Test
+ public void testXmlContent() throws IOException{
+ when(ahc.createTransformer(String.class, ContentType.XML)).thenReturn(transformer);
+ when(transformer.marshall("")).thenReturn(ByteSource.wrap("".getBytes(Charsets.UTF_8)));
+ Content content = request.xmlContent("").getContent();
+ assertThat(content, instanceOf(ByteSourceContent.class));
+ ByteSourceContent bsc = (ByteSourceContent) content;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ bsc.process(baos);
+ assertEquals("", baos.toString("UTF-8"));
+ }
+
+ @Test
+ public void testJsonContent() throws IOException{
+ when(ahc.createTransformer(String.class, ContentType.JSON)).thenReturn(transformer);
+ when(transformer.marshall("{}")).thenReturn(ByteSource.wrap("{'root': {}}".getBytes(Charsets.UTF_8)));
+ Content content = request.jsonContent("{}").getContent();
+ assertThat(content, instanceOf(ByteSourceContent.class));
+ ByteSourceContent bsc = (ByteSourceContent) content;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ bsc.process(baos);
+ assertEquals("{'root': {}}", baos.toString("UTF-8"));
+ }
+
+ @Test
+ public void testTransformedContent() throws IOException{
+ when(ahc.createTransformer(String.class, "text/plain")).thenReturn(transformer);
+ when(transformer.marshall("hello")).thenReturn(ByteSource.wrap("hello world".getBytes(Charsets.UTF_8)));
+ Content content = request.transformedContent("text/plain", "hello").getContent();
+ assertThat(content, instanceOf(ByteSourceContent.class));
+ ByteSourceContent bsc = (ByteSourceContent) content;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ bsc.process(baos);
+ assertEquals("hello world", baos.toString("UTF-8"));
+ }
+
+ @Test
+ public void testSelf()
+ {
+ assertEquals(AdvancedHttpRequestWithBody.class, request.self().getClass());
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java
new file mode 100644
index 0000000000..6fc149f36c
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java
@@ -0,0 +1,214 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharStreams;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AdvancedHttpResponseTest {
+
+ @Mock(answer = Answers.CALLS_REAL_METHODS)
+ private AdvancedHttpResponse response;
+
+ @Mock
+ private ContentTransformer transformer;
+
+ @Test
+ public void testContent() throws IOException
+ {
+ ByteSource bs = ByteSource.wrap("test123".getBytes(Charsets.UTF_8));
+ when(response.contentAsByteSource()).thenReturn(bs);
+ byte[] data = response.content();
+ assertEquals("test123", new String(data, Charsets.UTF_8));
+ }
+
+ @Test
+ public void testContentWithoutByteSource() throws IOException
+ {
+ assertNull(response.content());
+ }
+
+ @Test
+ public void testContentAsString() throws IOException
+ {
+ ByteSource bs = ByteSource.wrap("123test".getBytes(Charsets.UTF_8));
+ when(response.contentAsByteSource()).thenReturn(bs);
+ assertEquals("123test", response.contentAsString());
+ }
+
+ @Test
+ public void testContentAsStingWithoutByteSource() throws IOException
+ {
+ assertNull(response.contentAsString());
+ }
+
+ @Test
+ public void testContentAsReader() throws IOException
+ {
+ ByteSource bs = ByteSource.wrap("abc123".getBytes(Charsets.UTF_8));
+ when(response.contentAsByteSource()).thenReturn(bs);
+ assertEquals("abc123", CharStreams.toString(response.contentAsReader()));
+ }
+
+ @Test
+ public void testContentAsReaderWithoutByteSource() throws IOException
+ {
+ assertNull(response.contentAsReader());
+ }
+
+ @Test
+ public void testContentAsStream() throws IOException
+ {
+ ByteSource bs = ByteSource.wrap("cde456".getBytes(Charsets.UTF_8));
+ when(response.contentAsByteSource()).thenReturn(bs);
+ byte[] data = ByteStreams.toByteArray(response.contentAsStream());
+ assertEquals("cde456", new String(data, Charsets.UTF_8));
+ }
+
+ @Test
+ public void testContentAsStreamWithoutByteSource() throws IOException
+ {
+ assertNull(response.contentAsStream());
+ }
+
+ @Test
+ public void testContentFromJson() throws IOException{
+ ByteSource bs = ByteSource.wrap("{}".getBytes(Charsets.UTF_8));
+ when(response.contentAsByteSource()).thenReturn(bs);
+ when(response.createTransformer(String.class, ContentType.JSON)).thenReturn(transformer);
+ when(transformer.unmarshall(String.class, bs)).thenReturn("{root: null}");
+ String c = response.contentFromJson(String.class);
+ assertEquals("{root: null}", c);
+ }
+
+ @Test
+ public void testContentFromXml() throws IOException{
+ ByteSource bs = ByteSource.wrap("".getBytes(Charsets.UTF_8));
+ when(response.contentAsByteSource()).thenReturn(bs);
+ when(response.createTransformer(String.class, ContentType.XML)).thenReturn(transformer);
+ when(transformer.unmarshall(String.class, bs)).thenReturn("");
+ String c = response.contentFromXml(String.class);
+ assertEquals("", c);
+ }
+
+ @Test(expected = ContentTransformerException.class)
+ public void testContentTransformedWithoutHeader() throws IOException{
+ Multimap map = LinkedHashMultimap.create();
+ when(response.getHeaders()).thenReturn(map);
+ response.contentTransformed(String.class);
+ }
+
+ @Test
+ public void testContentTransformedFromHeader() throws IOException{
+ Multimap map = LinkedHashMultimap.create();
+ map.put("Content-Type", "text/plain");
+ when(response.getHeaders()).thenReturn(map);
+ when(response.createTransformer(String.class, "text/plain")).thenReturn(
+ transformer);
+ ByteSource bs = ByteSource.wrap("hello".getBytes(Charsets.UTF_8));
+ when(response.contentAsByteSource()).thenReturn(bs);
+ when(transformer.unmarshall(String.class, bs)).thenReturn("hello world");
+ String v = response.contentTransformed(String.class);
+ assertEquals("hello world", v);
+ }
+
+ @Test
+ public void testContentTransformed() throws IOException{
+ when(response.createTransformer(String.class, "text/plain")).thenReturn(
+ transformer);
+ ByteSource bs = ByteSource.wrap("hello".getBytes(Charsets.UTF_8));
+ when(response.contentAsByteSource()).thenReturn(bs);
+ when(transformer.unmarshall(String.class, bs)).thenReturn("hello world");
+ String v = response.contentTransformed(String.class, "text/plain");
+ assertEquals("hello world", v);
+ }
+
+ @Test
+ public void testContentTransformedWithoutByteSource() throws IOException{
+ assertNull(response.contentTransformed(String.class, "text/plain"));
+ }
+
+ @Test
+ public void testGetFirstHeader() throws IOException
+ {
+ Multimap mm = LinkedHashMultimap.create();
+ mm.put("Test", "One");
+ mm.put("Test-2", "One");
+ mm.put("Test-2", "Two");
+ when(response.getHeaders()).thenReturn(mm);
+ assertEquals("One", response.getFirstHeader("Test"));
+ assertEquals("One", response.getFirstHeader("Test-2"));
+ assertNull(response.getFirstHeader("Test-3"));
+ }
+
+ @Test
+ public void testIsSuccessful() throws IOException
+ {
+ // successful
+ when(response.getStatus()).thenReturn(200);
+ assertTrue(response.isSuccessful());
+ when(response.getStatus()).thenReturn(201);
+ assertTrue(response.isSuccessful());
+ when(response.getStatus()).thenReturn(204);
+ assertTrue(response.isSuccessful());
+ when(response.getStatus()).thenReturn(301);
+ assertTrue(response.isSuccessful());
+
+ // not successful
+ when(response.getStatus()).thenReturn(400);
+ assertFalse(response.isSuccessful());
+ when(response.getStatus()).thenReturn(404);
+ assertFalse(response.isSuccessful());
+ when(response.getStatus()).thenReturn(500);
+ assertFalse(response.isSuccessful());
+ when(response.getStatus()).thenReturn(199);
+ assertFalse(response.isSuccessful());
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java
new file mode 100644
index 0000000000..9b3ce995f5
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import java.io.IOException;
+import java.util.Collection;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class BaseHttpRequestTest {
+
+ @Mock
+ private AdvancedHttpClient ahc;
+
+ private BaseHttpRequest request;
+
+ @Before
+ public void before(){
+ request = new AdvancedHttpRequest(ahc, HttpMethod.GET, "https://www.scm-manager.org");
+ }
+
+ @Test
+ public void testBasicAuth()
+ {
+ request.basicAuth("tricia", "mcmillian123");
+ Multimap headers = request.getHeaders();
+ assertEquals("Basic dHJpY2lhOm1jbWlsbGlhbjEyMw==", headers.get("Authorization").iterator().next());
+ }
+
+ @Test
+ public void testQueryString(){
+ request.queryString("a", "b");
+ assertEquals("https://www.scm-manager.org?a=b", request.getUrl());
+ }
+
+ @Test
+ public void testQueryStringMultiple(){
+ request.queryString("a", "b");
+ request.queryString("c", "d", "e");
+ assertEquals("https://www.scm-manager.org?a=b&c=d&c=e", request.getUrl());
+ }
+
+ @Test
+ public void testQueryStringEncoded(){
+ request.queryString("a", "äüö");
+ assertEquals("https://www.scm-manager.org?a=%C3%A4%C3%BC%C3%B6", request.getUrl());
+ }
+
+ @Test
+ public void testQueryStrings(){
+ Iterable extends Object> i1 = Lists.newArrayList("b");
+ Iterable extends Object> i2 = Lists.newArrayList("d", "e");
+ request.queryStrings("a", i1);
+ request.queryStrings("c", i2);
+ assertEquals("https://www.scm-manager.org?a=b&c=d&c=e", request.getUrl());
+ }
+
+ @Test
+ public void testQuerqStringNullValue(){
+ request.queryString("a", null, "b");
+ assertEquals("https://www.scm-manager.org?a=&a=b", request.getUrl());
+ }
+
+ @Test
+ public void testHeader(){
+ request.header("a", "b");
+ assertEquals("b", request.getHeaders().get("a").iterator().next());
+ }
+
+ @Test
+ public void testHeaderMultiple(){
+ request.header("a", "b", "c", "d");
+ Collection values = request.getHeaders().get("a");
+ assertThat(values, contains("b", "c", "d"));
+ }
+
+ @Test
+ public void testRequest() throws IOException{
+ request.request();
+ verify(ahc).request(request);
+ }
+
+ @Test
+ public void testBuilderMethods(){
+ Iterable extends Object> i1 = Lists.newArrayList("b");
+ assertThat(request.decodeGZip(true), instanceOf(AdvancedHttpRequest.class));
+ assertTrue(request.isDecodeGZip());
+ assertThat(request.disableCertificateValidation(true), instanceOf(AdvancedHttpRequest.class));
+ assertTrue(request.isDisableCertificateValidation());
+ assertThat(request.disableHostnameValidation(true), instanceOf(AdvancedHttpRequest.class));
+ assertTrue(request.isDisableHostnameValidation());
+ assertThat(request.ignoreProxySettings(true), instanceOf(AdvancedHttpRequest.class));
+ assertTrue(request.isIgnoreProxySettings());
+ assertThat(request.header("a", "b"), instanceOf(AdvancedHttpRequest.class));
+ assertThat(request.headers("a", i1), instanceOf(AdvancedHttpRequest.class));
+ assertThat(request.queryString("a", "b"), instanceOf(AdvancedHttpRequest.class));
+ assertThat(request.queryStrings("a", i1), instanceOf(AdvancedHttpRequest.class));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java
new file mode 100644
index 0000000000..24487f6582
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteSource;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ByteSourceContentTest {
+
+ @Mock
+ private AdvancedHttpRequestWithBody request;
+
+ @Test
+ public void testPrepareRequest() throws IOException
+ {
+ ByteSource source = ByteSource.wrap("abc".getBytes(Charsets.UTF_8));
+ ByteSourceContent content = new ByteSourceContent(source);
+ content.prepare(request);
+ verify(request).contentLength(3l);
+ }
+
+ @Test
+ public void testProcess() throws IOException{
+ ByteSource source = ByteSource.wrap("abc".getBytes(Charsets.UTF_8));
+ ByteSourceContent content = new ByteSourceContent(source);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ content.process(baos);
+ assertEquals("abc", baos.toString("UTF-8"));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java
new file mode 100644
index 0000000000..95b381b880
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.*;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FileContentTest
+{
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testPrepareRequest() throws IOException
+ {
+ FileContent content = create("abc");
+
+ content.prepare(request);
+ verify(request).contentLength(3l);
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testProcess() throws IOException
+ {
+ FileContent content = create("abc");
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ content.process(baos);
+ assertEquals("abc", baos.toString("UTF-8"));
+ }
+
+ private FileContent create(String value) throws IOException
+ {
+ File file = temp.newFile();
+
+ Files.write("abc", file, Charsets.UTF_8);
+
+ return new FileContent(file);
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** Field description */
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ /** Field description */
+ @Mock
+ private AdvancedHttpRequestWithBody request;
+}
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java
new file mode 100644
index 0000000000..56cc6d7379
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FormContentBuilderTest {
+
+ @Mock
+ private AdvancedHttpRequestWithBody request;
+
+ @InjectMocks
+ private FormContentBuilder builder;
+
+ @Test
+ public void testFieldEncoding()
+ {
+ builder.field("a", "ü", "ä", "ö").build();
+ assertContent("a=%C3%BC&a=%C3%A4&a=%C3%B6");
+ }
+
+ @Test
+ public void testBuild()
+ {
+ builder.field("a", "b").build();
+ assertContent("a=b");
+ verify(request).contentType("application/x-www-form-urlencoded");
+ }
+
+ @Test
+ public void testFieldWithArray()
+ {
+ builder.field("a", "b").field("c", "d", "e").build();
+ assertContent("a=b&c=d&c=e");
+ }
+
+ @Test
+ public void testFieldWithIterable()
+ {
+ Iterable extends Object> i1 = Lists.newArrayList("b");
+ builder.fields("a", i1)
+ .fields("c", Lists.newArrayList("d", "e"))
+ .build();
+ assertContent("a=b&c=d&c=e");
+ }
+
+ private void assertContent(String content){
+ ArgumentCaptor captor = ArgumentCaptor.forClass(String.class);
+ verify(request).stringContent(captor.capture());
+ assertEquals(content, captor.getValue());
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java
new file mode 100644
index 0000000000..f4ee8b7031
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.base.Charsets;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class RawContentTest {
+
+
+ @Mock
+ private AdvancedHttpRequestWithBody request;
+
+ @Test
+ public void testPrepareRequest()
+ {
+ RawContent raw = new RawContent("abc".getBytes(Charsets.UTF_8));
+ raw.prepare(request);
+ verify(request).contentLength(3);
+ }
+
+ @Test
+ public void testProcess() throws IOException
+ {
+ RawContent raw = new RawContent("abc".getBytes(Charsets.UTF_8));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ raw.process(baos);
+ assertEquals("abc", baos.toString("UTF-8"));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java
new file mode 100644
index 0000000000..bd191438ca
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.base.Charsets;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+public class StringContentTest {
+
+
+ @Test
+ public void testStringContent()
+ {
+ StringContent sc = new StringContent("abc", Charsets.UTF_8);
+ assertEquals("abc", new String(sc.getData()));
+ }
+
+ @Test
+ public void testStringContentWithCharset()
+ {
+ StringContent sc = new StringContent("üäö", Charsets.ISO_8859_1);
+ assertEquals("üäö", new String(sc.getData(), Charsets.ISO_8859_1));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/repository/api/HookContextTest.java b/scm-core/src/test/java/sonia/scm/repository/api/HookContextTest.java
new file mode 100644
index 0000000000..2f746ec5ad
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/repository/api/HookContextTest.java
@@ -0,0 +1,141 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.repository.api;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.List;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.repository.Changeset;
+import sonia.scm.repository.Person;
+import sonia.scm.repository.PreProcessorUtil;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.spi.HookChangesetProvider;
+import sonia.scm.repository.spi.HookChangesetRequest;
+import sonia.scm.repository.spi.HookChangesetResponse;
+import sonia.scm.repository.spi.HookContextProvider;
+
+/**
+ * Unit tests for {@link HookContext}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class HookContextTest {
+
+ @Mock
+ private HookContextProvider provider;
+
+ @Mock
+ private Repository repository;
+
+ @Mock
+ private PreProcessorUtil preProcessorUtil;
+
+ @Mock
+ private HookChangesetProvider changesetProvider;
+
+ @InjectMocks
+ private HookContext context;
+
+ /**
+ * Set up mocks for upcoming test.
+ */
+ @Before
+ public void setUpMocks(){
+ when(repository.getName()).thenReturn("test");
+ when(provider.getChangesetProvider()).thenReturn(changesetProvider);
+ when(provider.getSupportedFeatures()).thenReturn(Sets.newHashSet(HookFeature.CHANGESET_PROVIDER));
+
+ List changesets = Lists.newArrayList(new Changeset("1", Long.MIN_VALUE, new Person("Trillian")));
+ HookChangesetResponse response = new HookChangesetResponse(changesets);
+ when(changesetProvider.handleRequest(any(HookChangesetRequest.class))).thenReturn(response);
+ }
+
+ /**
+ * Tests {@link HookContext#getBranchProvider()}.
+ */
+ @Test
+ public void testGetBranchProvider() {
+ context.getBranchProvider();
+
+ verify(provider).getBranchProvider();
+ }
+
+ /**
+ * Tests {@link HookContext#getTagProvider()}.
+ */
+ @Test
+ public void testGetTagProvider() {
+ context.getTagProvider();
+
+ verify(provider).getTagProvider();
+ }
+
+ /**
+ * Tests {@link HookContext#getMessageProvider()}.
+ */
+ @Test
+ public void testGetMessageProvider() {
+ context.getMessageProvider();
+
+ verify(provider).getMessageProvider();
+ }
+
+ /**
+ * Tests {@link HookContext#getChangesetProvider()}.
+ */
+ @Test
+ public void testGetChangesetProvider() {
+ HookChangesetBuilder builder = context.getChangesetProvider();
+ List changesets = builder.getChangesetList();
+ assertNotNull(changesets);
+ assertEquals("1", changesets.get(0).getId());
+ }
+
+ /**
+ * Tests {@link HookContext#isFeatureSupported(sonia.scm.repository.api.HookFeature)}.
+ */
+ @Test
+ public void testIsFeatureSupported(){
+ assertTrue(context.isFeatureSupported(HookFeature.CHANGESET_PROVIDER));
+ assertFalse(context.isFeatureSupported(HookFeature.BRANCH_PROVIDER));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/repository/spi/HookContextProviderTest.java b/scm-core/src/test/java/sonia/scm/repository/spi/HookContextProviderTest.java
new file mode 100644
index 0000000000..c6abae594e
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/repository/spi/HookContextProviderTest.java
@@ -0,0 +1,136 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.repository.spi;
+
+import java.util.Collections;
+import java.util.Set;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import sonia.scm.repository.api.HookException;
+import sonia.scm.repository.api.HookFeature;
+import sonia.scm.repository.api.HookFeatureIsNotSupportedException;
+
+/**
+ * Unit tests for {@link HookContextProvider}.
+ *
+ * @author Sebastian Sdorra
+ */
+public class HookContextProviderTest {
+
+ /**
+ * Expected exception rule.
+ */
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private final HookContextProvider simpleHookContextProvider = new HookContextProvider() {
+
+ @Override
+ public Set getSupportedFeatures() {
+ return Collections.emptySet();
+ }
+
+ };
+
+ /**
+ * Tests {@link HookContextProvider#getSupportedFeatures()}.
+ */
+ @Test
+ public void testGetSupportedFeatures() {
+ assertThat(simpleHookContextProvider.getSupportedFeatures(), empty());
+ }
+
+ /**
+ * Tests {@link HookContextProvider#getBranchProvider()}.
+ */
+ @Test
+ public void testGetBranchProvider(){
+ expectNotSupported(HookFeature.BRANCH_PROVIDER);
+ simpleHookContextProvider.getBranchProvider();
+ }
+
+ /**
+ * Tests {@link HookContextProvider#getTagProvider()}.
+ */
+ @Test
+ public void testGetTagProvider(){
+ expectNotSupported(HookFeature.TAG_PROVIDER);
+ simpleHookContextProvider.getTagProvider();
+ }
+
+ /**
+ * Tests {@link HookContextProvider#getChangesetProvider()}.
+ */
+ @Test
+ public void testGetChangesetProvider(){
+ expectNotSupported(HookFeature.CHANGESET_PROVIDER);
+ simpleHookContextProvider.getChangesetProvider();
+ }
+
+ /**
+ * Tests {@link HookContextProvider#createMessageProvider()}.
+ */
+ @Test
+ public void testCreateMessageProvider(){
+ expectNotSupported(HookFeature.MESSAGE_PROVIDER);
+ simpleHookContextProvider.createMessageProvider();
+ }
+
+ /**
+ * Tests {@link HookContextProvider#getMessageProvider()}.
+ */
+ @Test
+ public void testGetMessageProvider(){
+ expectNotSupported(HookFeature.MESSAGE_PROVIDER);
+ simpleHookContextProvider.getMessageProvider();
+ }
+
+ /**
+ * Tests {@link HookContextProvider#getMessageProvider()} with disconnected client.
+ */
+ @Test
+ public void testGetMessageProviderDisconnected(){
+ expectedException.expect(HookException.class);
+ expectedException.expectMessage(containsString("message provider"));
+ simpleHookContextProvider.handleClientDisconnect();
+ simpleHookContextProvider.getMessageProvider();
+ }
+
+ private void expectNotSupported(HookFeature feature){
+ expectedException.expect(HookFeatureIsNotSupportedException.class);
+ expectedException.expectMessage(containsString(feature.toString()));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/security/DefaultCipherHandlerTest.java b/scm-core/src/test/java/sonia/scm/security/DefaultCipherHandlerTest.java
new file mode 100644
index 0000000000..1fe78191de
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/security/DefaultCipherHandlerTest.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.security;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.SCMContextProvider;
+
+/**
+ * Unit tests for {@link DefaultCipherHandler}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultCipherHandlerTest {
+
+ @Mock
+ private SCMContextProvider context;
+
+ @Mock
+ private KeyGenerator keyGenerator;
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ /**
+ * Tests loading and storing default key.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testLoadingAndStoringDefaultKey() throws IOException {
+ File baseDirectory = tempFolder.newFolder();
+ when(context.getBaseDirectory()).thenReturn(baseDirectory);
+ when(keyGenerator.createKey()).thenReturn("secret");
+
+ DefaultCipherHandler cipher = new DefaultCipherHandler(context, keyGenerator);
+ File configDirectory = new File(baseDirectory, "config");
+ assertTrue(new File(configDirectory, DefaultCipherHandler.CIPHERKEY_FILENAME).exists());
+
+ // plain text for assertion
+ String plain = "hallo123";
+
+ // encrypt value with new generated key
+ String encrypted = cipher.encode(plain);
+
+ // load key from disk
+ cipher = new DefaultCipherHandler(context, keyGenerator);
+
+ // decrypt with loaded key
+ assertEquals(plain, cipher.decode(encrypted));
+ }
+
+ /**
+ * Test encode and decode method with a separate key.
+ */
+ @Test
+ public void testEncodeDecodeWithSeparateKey(){
+ char[] key = "testkey".toCharArray();
+ DefaultCipherHandler cipher = new DefaultCipherHandler("somekey");
+ assertEquals("hallo123", cipher.decode(key, cipher.encode(key, "hallo123")));
+ }
+
+ /**
+ * Test encode and decode method with the default key.
+ */
+ @Test
+ public void testEncodeDecodeWithDefaultKey() {
+ DefaultCipherHandler cipher = new DefaultCipherHandler("testkey");
+ assertEquals("hallo123", cipher.decode(cipher.encode("hallo123")));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java b/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java
index 585bc0e005..b85221f8c8 100644
--- a/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java
+++ b/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java
@@ -168,6 +168,136 @@ public class HttpUtilTest
HttpUtil.checkForCRLFInjection("abcka");
}
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testCreateBaseUrl()
+ {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ String url = "https://www.scm-manager.org/test/as/db";
+
+ when(request.getRequestURL()).thenReturn(new StringBuffer(url));
+ when(request.getRequestURI()).thenReturn("/test/as/db");
+ when(request.getContextPath()).thenReturn("/scm");
+ assertEquals("https://www.scm-manager.org/scm",
+ HttpUtil.createBaseUrl(request));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testCreateForwardedUrl()
+ {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn(
+ "www.scm-manager.org");
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_PROTO)).thenReturn(
+ "https");
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_PORT)).thenReturn("443");
+ when(request.getContextPath()).thenReturn("/scm");
+ assertEquals("https://www.scm-manager.org:443/scm",
+ HttpUtil.createForwardedBaseUrl(request));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testCreateForwardedUrlWithPortAndProtoFromRequest()
+ {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn(
+ "www.scm-manager.org");
+ when(request.getScheme()).thenReturn("https");
+ when(request.getServerPort()).thenReturn(443);
+ when(request.getContextPath()).thenReturn("/scm");
+ assertEquals("https://www.scm-manager.org:443/scm",
+ HttpUtil.createForwardedBaseUrl(request));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testCreateForwardedUrlWithPortInHost()
+ {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn(
+ "www.scm-manager.org:443");
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_PROTO)).thenReturn(
+ "https");
+ when(request.getContextPath()).thenReturn("/scm");
+ assertEquals("https://www.scm-manager.org:443/scm",
+ HttpUtil.createForwardedBaseUrl(request));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testCreateForwardedUrlWithPortInHostAndPortHeader()
+ {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn(
+ "www.scm-manager.org:80");
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_PROTO)).thenReturn(
+ "https");
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_PORT)).thenReturn("443");
+ when(request.getContextPath()).thenReturn("/scm");
+ assertEquals("https://www.scm-manager.org:443/scm",
+ HttpUtil.createForwardedBaseUrl(request));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testGetCompleteUrl()
+ {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ String url = "https://www.scm-manager.org/test/as/db";
+
+ when(request.getRequestURL()).thenReturn(new StringBuffer(url));
+ when(request.getRequestURI()).thenReturn("/test/as/db");
+ when(request.getScheme()).thenReturn("https");
+ when(request.getServerPort()).thenReturn(443);
+ when(request.getContextPath()).thenReturn("/scm");
+ assertEquals("https://www.scm-manager.org/scm",
+ HttpUtil.getCompleteUrl(request));
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn(
+ "scm.scm-manager.org");
+ assertEquals("https://scm.scm-manager.org:443/scm",
+ HttpUtil.getCompleteUrl(request));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testIsForwarded()
+ {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+
+ assertFalse(HttpUtil.isForwarded(request));
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn("");
+ assertFalse(HttpUtil.isForwarded(request));
+ when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn("ser");
+ assertTrue(HttpUtil.isForwarded(request));
+ }
+
/**
* Method description
*
diff --git a/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java b/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java
index fe64cb2d4f..16d4b59ffc 100644
--- a/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java
+++ b/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java
@@ -57,8 +57,9 @@ public class UrlBuilderTest
builder.appendParameter("i", 123).appendParameter("s", "abc");
builder.appendParameter("b", true).appendParameter("l", 321l);
- assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321",
- builder.toString());
+ assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321", builder.toString());
+ builder.appendParameter("c", "a b");
+ assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321&c=a%20b", builder.toString());
}
/**
diff --git a/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java b/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java
new file mode 100644
index 0000000000..d3f917932c
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java
@@ -0,0 +1,187 @@
+/**
+* Copyright (c) 2010, Sebastian Sdorra
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright notice,
+* this list of conditions and the following disclaimer in the documentation
+* and/or other materials provided with the distribution.
+* 3. Neither the name of SCM-Manager; nor the names of its
+* contributors may be used to endorse or promote products derived from this
+* software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+* http://bitbucket.org/sdorra/scm-manager
+*
+*/
+
+
+
+package sonia.scm.web;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Sets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import sonia.scm.cache.Cache;
+import sonia.scm.cache.CacheManager;
+import sonia.scm.util.HttpUtil;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.*;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@SuppressWarnings("unchecked")
+@RunWith(MockitoJUnitRunner.class)
+public class UserAgentParserTest
+{
+
+ /** Field description */
+ private static final String UA_1 = "mozilla/5.0";
+
+ /** Field description */
+ private static final String UA_2 = "wget/1.5.3";
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Method description
+ *
+ */
+ @Before
+ public void prepare()
+ {
+ Set providers = Sets.newHashSet(provider1, provider2);
+ when(cacheManager.getCache(UserAgentParser.CACHE_NAME)).thenReturn(cache);
+ parser = new UserAgentParser(providers, cacheManager);
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testDefaultValues()
+ {
+ UserAgent ua = parser.parse(UA_1);
+
+ assertEquals(Charsets.ISO_8859_1, ua.getBasicAuthenticationCharset());
+ assertTrue(ua.isBrowser());
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testParse()
+ {
+ UserAgent ua = UserAgent.builder("UA1").build();
+
+ when(provider1.parseUserAgent(UA_1)).thenReturn(ua);
+
+ UserAgent ua2 = UserAgent.builder("UA2").build();
+
+ when(provider2.parseUserAgent(UA_2)).thenReturn(ua2);
+
+ assertEquals(ua, parser.parse(UA_1));
+ assertEquals(ua2, parser.parse(UA_2));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testParseHttpServletRequest()
+ {
+ when(request.getHeader(HttpUtil.HEADER_USERAGENT)).thenReturn(UA_2);
+
+ UserAgent ua = UserAgent.builder("UA2").build();
+
+ when(provider1.parseUserAgent(UA_2)).thenReturn(ua);
+ assertEquals(ua, parser.parse(request));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testParseNotFound()
+ {
+ assertEquals(UserAgentParser.UNKNOWN, parser.parse(UA_1));
+ assertEquals(UserAgentParser.UNKNOWN, parser.parse(UA_2));
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testParseWithCache()
+ {
+ UserAgent ua = UserAgent.builder("UA").build();
+
+ when(cache.get(UA_1)).thenReturn(ua);
+ assertEquals(ua, parser.parse(UA_1));
+ assertEquals(UserAgentParser.UNKNOWN, parser.parse(UA_2));
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** Field description */
+ @Mock
+ private Cache cache;
+
+ /** Field description */
+ @Mock
+ private CacheManager cacheManager;
+
+ /** Field description */
+ private UserAgentParser parser;
+
+ /** Field description */
+ @Mock
+ private UserAgentProvider provider1;
+
+ /** Field description */
+ @Mock
+ private UserAgentProvider provider2;
+
+ /** Field description */
+ @Mock
+ private HttpServletRequest request;
+}
diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml
index edb857cd0a..5b61882815 100644
--- a/scm-plugins/pom.xml
+++ b/scm-plugins/pom.xml
@@ -142,4 +142,4 @@
-
\ No newline at end of file
+
diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml
index aa8e783962..4fff4ddd27 100644
--- a/scm-plugins/scm-git-plugin/pom.xml
+++ b/scm-plugins/scm-git-plugin/pom.xml
@@ -37,12 +37,6 @@
org.eclipse.jgit.http.server${jgit.version}
-
-
- sonia.jgit
- org.eclipse.jgit.java7
- ${jgit.version}
- commons-lang
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java
index ab588816ce..a59e3b5754 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java
@@ -160,6 +160,23 @@ public class GitChangesetConverter implements Closeable
return createChangeset(commit, branches);
}
+ /**
+ * Method description
+ *
+ *
+ * @param commit
+ * @param branch
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+ public Changeset createChangeset(RevCommit commit, String branch)
+ throws IOException
+ {
+ return createChangeset(commit, Lists.newArrayList(branch));
+ }
+
/**
* Method description
*
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java
index 9f508effe6..80fe8907ac 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java
@@ -35,6 +35,9 @@ package sonia.scm.repository;
//~--- JDK imports ------------------------------------------------------------
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
@@ -42,4 +45,15 @@ import javax.xml.bind.annotation.XmlRootElement;
* @author Sebastian Sdorra
*/
@XmlRootElement(name = "config")
-public class GitConfig extends SimpleRepositoryConfig {}
+@XmlAccessorType(XmlAccessType.FIELD)
+public class GitConfig extends SimpleRepositoryConfig {
+
+ @XmlElement(name = "gc-expression")
+ private String gcExpression;
+
+ public String getGcExpression()
+ {
+ return gcExpression;
+ }
+
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConstants.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConstants.java
new file mode 100644
index 0000000000..6d833577e1
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConstants.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.repository;
+
+/**
+ * Constants for Git.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.50
+ */
+public final class GitConstants {
+
+ /**
+ * Default branch repository property.
+ */
+ public static final String PROPERTY_DEFAULT_BRANCH = "git.default-branch";
+
+ private GitConstants() {
+ }
+
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitGcTask.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitGcTask.java
new file mode 100644
index 0000000000..248eae92d1
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitGcTask.java
@@ -0,0 +1,164 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.repository;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Stopwatch;
+import com.google.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import org.eclipse.jgit.api.GarbageCollectCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Executes git gc on every git repository. Statistics of the gc process are logged to the info level. The task is
+ * disabled by default and must be enabled through the global git configuration.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.47
+ */
+public class GitGcTask implements Runnable {
+
+ private static final String SP = System.getProperty("line.seperator", "\n");
+
+ private static final Logger logger = LoggerFactory.getLogger(GitGcTask.class);
+
+ private final RepositoryManager repositoryManager;
+ private final RepositoryDirectoryHandler repositoryHandler;
+
+ @Inject
+ public GitGcTask(RepositoryManager repositoryManager)
+ {
+ this.repositoryManager = repositoryManager;
+ this.repositoryHandler = (RepositoryDirectoryHandler) repositoryManager.getHandler(GitRepositoryHandler.TYPE_NAME);
+ }
+
+ @Override
+ public void run()
+ {
+ for (Repository repository : repositoryManager.getAll())
+ {
+ handle(repository);
+ }
+ }
+
+ private void handle(Repository repository){
+ if (GitRepositoryHandler.TYPE_NAME.equals(repository.getType()))
+ {
+ if (repository.isValid() && repository.isHealthy())
+ {
+ logger.info("start git gc for repository {}", repository.getName());
+ Stopwatch sw = Stopwatch.createStarted();
+ gc(repository);
+ logger.debug("gc of repository {} has finished after {}", repository.getName(), sw.stop());
+ }
+ else
+ {
+ logger.debug("skip non valid/healthy repository {}", repository.getName());
+ }
+ }
+ else
+ {
+ logger.trace("skip non git repository {}", repository.getName());
+ }
+ }
+
+ private void appendProperties(StringBuilder buffer, Properties properties){
+ for (Map.Entry
-
-
- maven.tmatesoft.com
- tmatesoft release repository
- http://maven.tmatesoft.com/content/repositories/releases/
-
diff --git a/scm-test/src/main/java/sonia/scm/repository/client/GitRepositoryClient.java b/scm-test/src/main/java/sonia/scm/repository/client/GitRepositoryClient.java
index 9f73fcb21a..9eba91d0e0 100644
--- a/scm-test/src/main/java/sonia/scm/repository/client/GitRepositoryClient.java
+++ b/scm-test/src/main/java/sonia/scm/repository/client/GitRepositoryClient.java
@@ -476,19 +476,10 @@ public class GitRepositoryClient extends AbstractRepositoryClient
private RevCommit parseCommit(Ref branch)
throws MissingObjectException, IncorrectObjectTypeException, IOException
{
- final RevWalk rw = new RevWalk(repository);
- final RevCommit commit;
-
- try
+ try (RevWalk rw = new RevWalk(repository))
{
- commit = rw.parseCommit(branch.getObjectId());
+ return rw.parseCommit(branch.getObjectId());
}
- finally
- {
- rw.release();
- }
-
- return commit;
}
/**
diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml
index 7fcbfba541..a43548368f 100644
--- a/scm-webapp/pom.xml
+++ b/scm-webapp/pom.xml
@@ -180,19 +180,25 @@
commons-codec1.9
-
-
- asm
- asm
- 3.3.1
-
-
+
com.google.guavaguava${guava.version}
+
+ org.quartz-scheduler
+ quartz
+ ${quartz.version}
+
+
+ c3p0
+ c3p0
+
+
+
+
@@ -243,6 +249,44 @@
+
+ org.seleniumhq.selenium
+ selenium-java
+ ${selenium.version}
+ test
+
+
+
+ org.seleniumhq.selenium
+ selenium-firefox-driver
+ ${selenium.version}
+ test
+
+
+
+ org.seleniumhq.selenium
+ htmlunit-driver
+ 2.21
+ test
+
+
+
+ com.sun.jersey
+ jersey-client
+ ${jersey.version}
+ test
+
+
+
+ com.sun.jersey.contribs
+ jersey-apache-client
+ ${jersey.version}
+ test
+
+
+
+
+
com.github.sdorrashiro-unit
@@ -258,6 +302,13 @@
test
+
+ sonia.scm.plugins
+ scm-git-plugin
+ 2.0.0-SNAPSHOT
+ test
+
+
sonia.scm.pluginsscm-hg-plugin
@@ -266,6 +317,13 @@
test
+
+ sonia.scm.plugins
+ scm-hg-plugin
+ 2.0.0-SNAPSHOT
+ test
+
+
sonia.scm.pluginsscm-svn-plugin
@@ -275,30 +333,9 @@
- org.seleniumhq.selenium
- selenium-java
- ${selenium.version}
- test
-
-
-
- org.seleniumhq.selenium
- selenium-firefox-driver
- ${selenium.version}
- test
-
-
-
- com.sun.jersey
- jersey-client
- ${jersey.version}
- test
-
-
-
- com.sun.jersey.contribs
- jersey-apache-client
- ${jersey.version}
+ sonia.scm.plugins
+ scm-svn-plugin
+ 2.0.0-SNAPSHOTtest
@@ -472,7 +509,7 @@
org.eclipse.jettyjetty-maven-plugin
- ${jetty.version}
+ ${jetty.maven.version}8005STOP
@@ -507,14 +544,15 @@
DEVELOPMENTtarget/scm-itdefault
- 2.28.0
- 1.28
+ 2.53.1
+ 1.311.00.8.17Tomcat
+
release
@@ -597,7 +635,7 @@
org.eclipse.jettyjetty-maven-plugin
- ${jetty.version}
+ ${jetty.maven.version}8085STOP
@@ -606,6 +644,10 @@
scm.hometarget/scm-it
+
+ scm.stage
+ ${scm.stage}
+ ${project.basedir}/src/main/conf/jetty.xml0
@@ -637,6 +679,17 @@
selenium
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.3.2
+ test
+
+
+
+
@@ -668,7 +721,7 @@
org.eclipse.jettyjetty-maven-plugin
- ${jetty.version}
+ ${jetty.maven.version}8082
diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java
index f8bc248199..5824c2f0d6 100644
--- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java
+++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java
@@ -65,7 +65,9 @@ import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
+import sonia.scm.debug.DebugModule;
import sonia.scm.filter.WebElementModule;
+import sonia.scm.schedule.Scheduler;
/**
*
@@ -108,6 +110,8 @@ public class ScmContextListener extends GuiceServletContextListener
{
if ((globalInjector != null) &&!startupError)
{
+ // close Scheduler
+ IOUtil.close(globalInjector.getInstance(Scheduler.class));
// close RepositoryManager
IOUtil.close(globalInjector.getInstance(RepositoryManager.class));
@@ -259,6 +263,10 @@ public class ScmContextListener extends GuiceServletContextListener
);
appendModules(pluginLoader.getExtensionProcessor(), moduleList);
moduleList.addAll(overrides.getModules());
+
+ if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT){
+ moduleList.add(new DebugModule());
+ }
SCMContextProvider ctx = SCMContext.getContext();
diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java
index edf2eabdd6..57cd1c62c5 100644
--- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java
+++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java
@@ -128,6 +128,18 @@ import java.util.Map;
import javax.servlet.ServletContext;
import sonia.scm.store.ConfigurationStoreFactory;
+import javax.net.ssl.SSLContext;
+import sonia.scm.net.SSLContextProvider;
+import sonia.scm.net.ahc.AdvancedHttpClient;
+import sonia.scm.net.ahc.ContentTransformer;
+import sonia.scm.net.ahc.DefaultAdvancedHttpClient;
+import sonia.scm.net.ahc.JsonContentTransformer;
+import sonia.scm.net.ahc.XmlContentTransformer;
+import sonia.scm.schedule.QuartzScheduler;
+import sonia.scm.schedule.Scheduler;
+import sonia.scm.security.XsrfProtectionFilter;
+import sonia.scm.web.UserAgentParser;
+
/**
*
* @author Sebastian Sdorra
@@ -249,6 +261,9 @@ public class ScmServletModule extends JerseyServletModule
bind(PluginLoader.class).toInstance(pluginLoader);
bind(PluginManager.class, DefaultPluginManager.class);
+ // bind scheduler
+ bind(Scheduler.class).to(QuartzScheduler.class);
+
// note CipherUtil uses an other generator
bind(KeyGenerator.class).to(DefaultKeyGenerator.class);
bind(CipherHandler.class).toInstance(cu.getCipherHandler());
@@ -280,8 +295,18 @@ public class ScmServletModule extends JerseyServletModule
GroupManagerProvider.class);
bind(CGIExecutorFactory.class, DefaultCGIExecutorFactory.class);
+ // bind sslcontext provider
+ bind(SSLContext.class).toProvider(SSLContextProvider.class);
+
// bind httpclient
bind(HttpClient.class, URLHttpClient.class);
+
+ // bind ahc
+ Multibinder transformers =
+ Multibinder.newSetBinder(binder(), ContentTransformer.class);
+ transformers.addBinding().to(XmlContentTransformer.class);
+ transformers.addBinding().to(JsonContentTransformer.class);
+ bind(AdvancedHttpClient.class).to(DefaultAdvancedHttpClient.class);
// bind resourcemanager
if (context.getStage() == Stage.DEVELOPMENT)
@@ -310,13 +335,16 @@ public class ScmServletModule extends JerseyServletModule
// bind new hook api
bind(HookContextFactory.class);
bind(HookEventFacade.class);
+
+ // bind user-agent parser
+ bind(UserAgentParser.class);
// bind debug logging filter
if ("true".equalsIgnoreCase(System.getProperty(SYSTEM_PROPERTY_DEBUG_HTTP)))
{
filter(PATTERN_ALL).through(LoggingFilter.class);
}
-
+
// debug servlet
serve(PATTERN_DEBUG).with(DebugServlet.class);
diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java
index 9143488ccd..5d559afe62 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java
@@ -83,6 +83,7 @@ import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
+import sonia.scm.security.XsrfCookies;
/**
*
@@ -266,6 +267,9 @@ public class AuthenticationResource
public Response logout(@Context HttpServletRequest request,
@Context HttpServletResponse response)
{
+ // remove xsrf token
+ XsrfCookies.remove(request, response);
+
Subject subject = SecurityUtils.getSubject();
subject.logout();
diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java
index 674b44c158..74e8310e05 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java
@@ -553,7 +553,8 @@ public class RepositoryImportResource
}
catch (RepositoryAlreadyExistsException ex)
{
- logger.warn("a {} repository with the name {} already exists", ex);
+ logger.warn("a {} repository with the name {} already exists", type,
+ name);
throw new WebApplicationException(Response.Status.CONFLICT);
}
@@ -692,7 +693,7 @@ public class RepositoryImportResource
private void handleGenericCreationFailure(Exception ex, String type,
String name)
{
- logger.error(String.format("could not create repository {} with type {}",
+ logger.error(String.format("could not create repository %s with type %s",
type, name), ex);
throw new WebApplicationException(ex);
diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java
index 42de0f00bd..640721bc64 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java
@@ -1111,7 +1111,7 @@ public class RepositoryResource
*/
private String getContentDispositionName(String name)
{
- return "attachment; filename=\"".concat(name).concat("\"");
+ return HttpUtil.createContentDispositionAttachmentHeader(name);
}
/**
diff --git a/scm-webapp/src/main/java/sonia/scm/debug/DebugHook.java b/scm-webapp/src/main/java/sonia/scm/debug/DebugHook.java
new file mode 100644
index 0000000000..d75b455a95
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/debug/DebugHook.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.debug;
+
+import com.github.legman.ReferenceType;
+import com.github.legman.Subscribe;
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import javax.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.EagerSingleton;
+import sonia.scm.repository.Changeset;
+import sonia.scm.repository.PostReceiveRepositoryHookEvent;
+
+/**
+ * {@link PostReceiveRepositoryHookEvent} which stores receives data and passes it to the {@link DebugService}.
+ *
+ * @author Sebastian Sdorra
+ */
+@EagerSingleton
+public final class DebugHook
+{
+ /**
+ * the logger for DebugHook
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(DebugHook.class);
+
+ private final DebugService debugService;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param debugService debug service
+ */
+ @Inject
+ public DebugHook(DebugService debugService)
+ {
+ this.debugService = debugService;
+ }
+
+ /**
+ * Processes the received {@link PostReceiveRepositoryHookEvent} and transforms it to a {@link DebugHookData} and
+ * passes it to the {@link DebugService}.
+ *
+ * @param event received event
+ */
+ @Subscribe(referenceType = ReferenceType.STRONG)
+ public void processEvent(PostReceiveRepositoryHookEvent event){
+ LOG.trace("store changeset ids from repository", event.getRepository().getId());
+
+ debugService.put(
+ event.getRepository().getId(),
+ new DebugHookData(Collections2.transform(
+ event.getContext().getChangesetProvider().getChangesetList(), IDEXTRACTOR)
+ ));
+ }
+
+ private static final Function IDEXTRACTOR = (Changeset changeset) -> changeset.getId();
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/debug/DebugHookData.java b/scm-webapp/src/main/java/sonia/scm/debug/DebugHookData.java
new file mode 100644
index 0000000000..bb8cfa42e7
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/debug/DebugHookData.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.debug;
+
+import java.util.Collection;
+import java.util.Date;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Received data from repository hook event.
+ *
+ * @author Sebastian Sdorra
+ */
+@XmlRootElement(name = "hook")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class DebugHookData
+{
+ private Date date;
+ private Collection changesets;
+
+ /**
+ * Constructs a new instance. This constructor should only be used by JAXB.
+ */
+ public DebugHookData()
+ {
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param changesets collection of changeset ids
+ */
+ public DebugHookData(Collection changesets)
+ {
+ this.date = new Date();
+ this.changesets = changesets;
+ }
+
+ /**
+ * Returns the receiving date.
+ *
+ * @return receiving date
+ */
+ public Date getDate()
+ {
+ return date;
+ }
+
+ /**
+ * Return collection of changeset ids.
+ *
+ * @return collection of changeset ids
+ */
+ public Collection getChangesets()
+ {
+ return changesets;
+ }
+
+
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/debug/DebugModule.java b/scm-webapp/src/main/java/sonia/scm/debug/DebugModule.java
new file mode 100644
index 0000000000..3c767a1ddb
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/debug/DebugModule.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.debug;
+
+import com.google.inject.AbstractModule;
+
+/**
+ * DebugModule binds all required classes around the {@link DebugService}. The module will only be activated, if the
+ * application was started in development stage.
+ *
+ * @author Sebastian Sdorra
+ */
+public final class DebugModule extends AbstractModule
+{
+
+ @Override
+ protected void configure()
+ {
+ bind(DebugService.class);
+ bind(DebugHook.class);
+ bind(DebugResource.class);
+ }
+
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/debug/DebugResource.java b/scm-webapp/src/main/java/sonia/scm/debug/DebugResource.java
new file mode 100644
index 0000000000..f65e0c7708
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/debug/DebugResource.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.debug;
+
+import java.util.Collection;
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Rest api resource for the {@link DebugService}.
+ *
+ * @author Sebastian Sdorra
+ */
+@Path("debug/{repository}/post-receive")
+public final class DebugResource
+{
+ private final DebugService debugService;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param debugService debug service
+ */
+ @Inject
+ public DebugResource(DebugService debugService)
+ {
+ this.debugService = debugService;
+ }
+
+ /**
+ * Returns all received hook data for the given repository.
+ *
+ * @param repository repository id
+ *
+ * @return all received hook data for the given repository
+ */
+ @GET
+ @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+ public Collection getAll(@PathParam("repository") String repository){
+ return debugService.getAll(repository);
+ }
+
+ /**
+ * Returns the last received hook data for the given repository.
+ *
+ * @param repository repository id
+ *
+ * @return the last received hook data for the given repository
+ */
+ @GET
+ @Path("last")
+ @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+ public DebugHookData getLast(@PathParam("repository") String repository){
+ return debugService.getLast(repository);
+ }
+
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/debug/DebugService.java b/scm-webapp/src/main/java/sonia/scm/debug/DebugService.java
new file mode 100644
index 0000000000..31282b6b08
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/debug/DebugService.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.debug;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.inject.Singleton;
+import java.util.Collection;
+import org.apache.shiro.SecurityUtils;
+import sonia.scm.security.Role;
+
+/**
+ * The DebugService stores and returns received data from repository hook events.
+ *
+ * @author Sebastian Sdorra
+ */
+@Singleton
+public final class DebugService
+{
+
+ private final Multimap receivedHooks = LinkedListMultimap.create();
+
+ /**
+ * Stores {@link DebugHookData} for the given repository.
+ *
+ * @param repository repository id
+ * @param hookData received hook data
+ */
+ void put(String repository, DebugHookData hookData)
+ {
+ receivedHooks.put(repository, hookData);
+ }
+
+ /**
+ * Returns the last received hook data for the given repository.
+ *
+ * @param repository repository id
+ *
+ * @return the last received hook data for the given repository
+ */
+ public DebugHookData getLast(String repository){
+ SecurityUtils.getSubject().checkRole(Role.ADMIN);
+ DebugHookData hookData = null;
+ Collection receivedHookData = receivedHooks.get(repository);
+ if (receivedHookData != null && ! receivedHookData.isEmpty()){
+ hookData = Iterables.getLast(receivedHookData);
+ }
+ return hookData;
+ }
+
+ /**
+ * Returns all received hook data for the given repository.
+ *
+ * @param repository repository id
+ *
+ * @return all received hook data for the given repository
+ */
+ public Collection getAll(String repository){
+ SecurityUtils.getSubject().checkRole(Role.ADMIN);
+ return receivedHooks.get(repository);
+ }
+
+}
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 ba41275324..b491c820ea 100644
--- a/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java
+++ b/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java
@@ -92,8 +92,9 @@ public class LegmanScmEventBus extends ScmEventBus
@Override
public void register(Object object)
{
- logger.debug("register {} to event bus", object);
+ logger.trace("register {} to event bus", object);
eventBus.register(object);
+
}
/**
@@ -105,8 +106,8 @@ public class LegmanScmEventBus extends ScmEventBus
@Override
public void unregister(Object object)
{
- logger.debug("unregister {} from event bus", object);
-
+ logger.trace("unregister {} from event bus", object);
+
try
{
eventBus.unregister(object);
diff --git a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java
index 75bcc55ad6..328fb3a2ae 100644
--- a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java
+++ b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java
@@ -44,7 +44,9 @@ import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.Role;
/**
- *
+ * Security filter which allow only administrators to access the underlying
+ * resources.
+ *
* @author Sebastian Sdorra
*/
@WebElement(
@@ -60,10 +62,9 @@ public class AdminSecurityFilter extends SecurityFilter
{
/**
- * Constructs ...
+ * Constructs a new instance.
*
- *
- * @param configuration
+ * @param configuration scm-manager main configuration
*/
@Inject
public AdminSecurityFilter(ScmConfiguration configuration)
@@ -74,12 +75,17 @@ public class AdminSecurityFilter extends SecurityFilter
//~--- get methods ----------------------------------------------------------
/**
- * Method description
- *
+ * Returns {@code true} if the subject has the admin role.
*
+<<<<<<< working copy
* @param subject
*
* @return
+=======
+ * @param subject subject
+ *
+ * @return {@code true} if the subject has the admin role
+>>>>>>> merge rev
*/
@Override
protected boolean hasPermission(Subject subject)
diff --git a/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java
index 8445a29f54..9198c19ef7 100644
--- a/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java
+++ b/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java
@@ -33,6 +33,7 @@ package sonia.scm.filter;
//~--- non-JDK imports --------------------------------------------------------
+import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Singleton;
import org.apache.shiro.SecurityUtils;
@@ -63,13 +64,24 @@ public class MDCFilter extends HttpFilter
{
/** Field description */
- private static final String MDC_CLIEN_HOST = "client_host";
+ @VisibleForTesting
+ static final String MDC_CLIEN_HOST = "client_host";
/** Field description */
- private static final String MDC_CLIEN_IP = "client_ip";
+ @VisibleForTesting
+ static final String MDC_CLIEN_IP = "client_ip";
+
+ /** url of the current request */
+ @VisibleForTesting
+ static final String MDC_REQUEST_URI = "request_uri";
+
+ /** request method */
+ @VisibleForTesting
+ static final String MDC_REQUEST_METHOD = "request_method";
/** Field description */
- private static final String MDC_USERNAME = "username";
+ @VisibleForTesting
+ static final String MDC_USERNAME = "username";
//~--- methods --------------------------------------------------------------
@@ -92,6 +104,8 @@ public class MDCFilter extends HttpFilter
MDC.put(MDC_USERNAME, getUsername());
MDC.put(MDC_CLIEN_IP, request.getRemoteAddr());
MDC.put(MDC_CLIEN_HOST, request.getRemoteHost());
+ MDC.put(MDC_REQUEST_METHOD, request.getMethod());
+ MDC.put(MDC_REQUEST_URI, request.getRequestURI());
try
{
@@ -102,6 +116,8 @@ public class MDCFilter extends HttpFilter
MDC.remove(MDC_USERNAME);
MDC.remove(MDC_CLIEN_IP);
MDC.remove(MDC_CLIEN_HOST);
+ MDC.remove(MDC_REQUEST_METHOD);
+ MDC.remove(MDC_REQUEST_URI);
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java
index be031f5ed1..7f5366ae1c 100644
--- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java
+++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java
@@ -35,8 +35,8 @@ package sonia.scm.filter;
//~--- non-JDK imports --------------------------------------------------------
+import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
-import com.google.inject.Singleton;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
@@ -44,7 +44,6 @@ import org.apache.shiro.subject.Subject;
import sonia.scm.Priority;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration;
-import sonia.scm.user.User;
import sonia.scm.web.filter.HttpFilter;
import sonia.scm.web.filter.SecurityHttpServletRequestWrapper;
@@ -62,11 +61,14 @@ import javax.servlet.http.HttpServletResponse;
* @author Sebastian Sdorra
*/
@Priority(Filters.PRIORITY_AUTHORIZATION)
-@WebElement(value = Filters.PATTERN_RESTAPI,
- morePatterns = { Filters.PATTERN_DEBUG })
+@WebElement(value = Filters.PATTERN_RESTAPI, morePatterns = { Filters.PATTERN_DEBUG })
public class SecurityFilter extends HttpFilter
{
+ /** name of request attribute for the primary principal */
+ @VisibleForTesting
+ static final String ATTRIBUTE_REMOTE_USER = "principal";
+
/** Field description */
public static final String URL_AUTHENTICATION = "/api/rest/authentication";
@@ -102,17 +104,21 @@ public class SecurityFilter extends HttpFilter
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException
{
- Subject subject = SecurityUtils.getSubject();
-
String uri =
request.getRequestURI().substring(request.getContextPath().length());
if (!uri.startsWith(URL_AUTHENTICATION))
{
+ Subject subject = SecurityUtils.getSubject();
if (hasPermission(subject))
{
- chain.doFilter(new SecurityHttpServletRequestWrapper(request,
- getUsername(subject)), response);
+ // add primary principal as request attribute
+ // see https://goo.gl/JRjNmf
+ String username = getUsername(subject);
+ request.setAttribute(ATTRIBUTE_REMOTE_USER, username);
+
+ // wrap servlet request to provide authentication informations
+ chain.doFilter(new SecurityHttpServletRequestWrapper(request, username), response);
}
else if (subject.isAuthenticated() || subject.isRemembered())
{
@@ -150,14 +156,6 @@ public class SecurityFilter extends HttpFilter
|| subject.isRemembered();
}
- /**
- * Method description
- *
- *
- * @param subject
- *
- * @return
- */
private String getUsername(Subject subject)
{
String username = SCMContext.USER_ANONYMOUS;
diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java
index 94a40a09f3..ef69e5c07d 100644
--- a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java
@@ -208,15 +208,14 @@ public class DefaultGroupManager extends AbstractGroupManager
String name = group.getName();
GroupPermissions.modify().check(name);
- Group oldGroup = groupDAO.get(name);
-
- if (oldGroup != null)
+ Group notModified = groupDAO.get(name);
+ if (notModified != null)
{
removeDuplicateMembers(group);
+ fireEvent(HandlerEventType.BEFORE_MODIFY, group, notModified);
group.setLastModified(System.currentTimeMillis());
- fireEvent(HandlerEventType.BEFORE_MODIFY, group, oldGroup);
groupDAO.modify(group);
- fireEvent(HandlerEventType.MODIFY, group, oldGroup);
+ fireEvent(HandlerEventType.MODIFY, group, notModified);
}
else
{
diff --git a/scm-webapp/src/main/java/sonia/scm/net/SSLContextProvider.java b/scm-webapp/src/main/java/sonia/scm/net/SSLContextProvider.java
new file mode 100644
index 0000000000..aadf8a2b13
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/SSLContextProvider.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.net;
+
+import com.google.common.base.Throwables;
+import com.google.inject.Inject;
+import java.security.NoSuchAlgorithmException;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.net.ssl.SSLContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provider for {@link SSLContext}. The provider will first try to retrieve the {@link SSLContext} from an "default"
+ * named optional provider, if this fails the provider will return the jvm default context.
+ *
+ * @author Sebastian Sdorra
+ * @version 1.47
+ */
+public final class SSLContextProvider implements Provider
+{
+
+ /**
+ * the logger for SSLContextProvider
+ */
+ private static final Logger logger = LoggerFactory.getLogger(SSLContextProvider.class);
+
+ @Named("default")
+ @Inject(optional = true)
+ private Provider sslContextProvider;
+
+ @Override
+ public SSLContext get()
+ {
+ SSLContext context = null;
+ if (sslContextProvider != null)
+ {
+ context = sslContextProvider.get();
+ }
+
+ if (context == null)
+ {
+ try
+ {
+ logger.trace("could not find ssl context provider, use jvm default");
+ context = SSLContext.getDefault();
+ }
+ catch (NoSuchAlgorithmException ex)
+ {
+ throw Throwables.propagate(ex);
+ }
+ }
+ else
+ {
+ logger.trace("use custom ssl context from provider");
+ }
+ return context;
+ }
+
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java
index 66c1fb9914..83b0fd23d5 100644
--- a/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java
+++ b/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java
@@ -48,8 +48,10 @@ import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import com.sun.jersey.core.util.Base64;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
@@ -67,11 +69,14 @@ import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
+import sonia.scm.util.HttpUtil;
/**
*
* @author Sebastian Sdorra
+ * @deprecated use {@link sonia.scm.net.ahc.AdvancedHttpClient}
*/
+@Deprecated
public class URLHttpClient implements HttpClient
{
@@ -152,6 +157,9 @@ public class URLHttpClient implements HttpClient
url);
connection.setRequestMethod(METHOD_POST);
+ // send empty content-length
+ // see issue #701 http://goo.gl/oyTdrA
+ setContentLength(connection, 0);
return new URLHttpResponse(connection);
}
@@ -347,41 +355,85 @@ public class URLHttpClient implements HttpClient
{
if (Util.isNotEmpty(parameters))
{
+ // use a ByteArrayOutputStream in order to get the final content-length
+ // see issue #701 http://goo.gl/oyTdrA
connection.setDoOutput(true);
OutputStreamWriter writer = null;
+ ByteArrayOutputStream baos = null;
try
{
- writer = new OutputStreamWriter(connection.getOutputStream());
+ baos = new ByteArrayOutputStream();
+ writer = new OutputStreamWriter(baos);
- Iterator>> it =
- parameters.entrySet().iterator();
-
- while (it.hasNext())
- {
- Map.Entry> p = it.next();
- List values = p.getValue();
-
- if (Util.isNotEmpty(values))
- {
- String key = encode(p.getKey());
-
- for (String value : values)
- {
- writer.append(key).append("=").append(encode(value));
- }
-
- if (it.hasNext())
- {
- writer.write("&");
- }
- }
- }
+ appendPostParameters(writer, parameters);
}
finally
{
IOUtil.close(writer);
+ IOUtil.close(baos);
+ }
+
+ if ( baos != null ){
+ byte[] data = baos.toByteArray();
+ appendBody(connection, data);
+ }
+ }
+ else
+ {
+ setContentLength(connection, 0);
+ }
+ }
+
+ private void appendBody(HttpURLConnection connection, byte[] body) throws IOException
+ {
+ int length = body.length;
+ logger.trace("write {} bytes to output", length);
+ setContentLength(connection, length);
+ connection.setFixedLengthStreamingMode(length);
+ OutputStream os = null;
+ try {
+ os = connection.getOutputStream();
+ os.write(body);
+ } finally {
+ IOUtil.close(os);
+ }
+ }
+
+ private void setContentLength(HttpURLConnection connection, int length)
+ {
+ connection.setRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, String.valueOf(length));
+ }
+
+ private void appendPostParameters(OutputStreamWriter writer, Map> parameters) throws IOException
+ {
+ Iterator>> it = parameters.entrySet().iterator();
+
+ while (it.hasNext())
+ {
+ Map.Entry> p = it.next();
+ List values = p.getValue();
+
+ if (Util.isNotEmpty(values))
+ {
+ String key = encode(p.getKey());
+
+ Iterator valueIt = values.iterator();
+
+ while(valueIt.hasNext())
+ {
+ String value = valueIt.next();
+ writer.append(key).append("=").append(encode(value));
+ if ( valueIt.hasNext() ){
+ writer.write("&");
+ }
+ }
+
+ if (it.hasNext())
+ {
+ writer.write("&");
+ }
}
}
}
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
new file mode 100644
index 0000000000..df6c93515b
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
@@ -0,0 +1,409 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.annotations.VisibleForTesting;
+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;
+
+/**
+ * Default implementation of the {@link AdvancedHttpClient}. The default
+ * implementation uses {@link HttpURLConnection}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class DefaultAdvancedHttpClient extends AdvancedHttpClient
+{
+
+ /** proxy authorization header */
+ @VisibleForTesting
+ static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization";
+
+ /** connection timeout */
+ @VisibleForTesting
+ static final int TIMEOUT_CONNECTION = 30000;
+
+ /** read timeout */
+ @VisibleForTesting
+ static final int TIMEOUT_RAED = 1200000;
+
+ /** credential separator */
+ private static final String CREDENTIAL_SEPARATOR = ":";
+
+ /** basic authentication prefix */
+ private static final String PREFIX_BASIC_AUTHENTICATION = "Basic ";
+
+ /**
+ * the logger for DefaultAdvancedHttpClient
+ */
+ private static final Logger logger =
+ LoggerFactory.getLogger(DefaultAdvancedHttpClient.class);
+
+ //~--- constructors ---------------------------------------------------------
+
+ /**
+ * Constructs a new {@link DefaultAdvancedHttpClient}.
+ *
+ *
+ * @param configuration scm-manager main configuration
+ * @param contentTransformers content transformer
+ * @param sslContextProvider ssl context provider
+ */
+ @Inject
+ public DefaultAdvancedHttpClient(ScmConfiguration configuration,
+ Set contentTransformers, Provider sslContextProvider)
+ {
+ this.configuration = configuration;
+ this.contentTransformers = contentTransformers;
+ this.sslContextProvider = sslContextProvider;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Creates a new {@link HttpURLConnection} from the given {@link URL}. The
+ * method is visible for testing.
+ *
+ *
+ * @param url url
+ *
+ * @return new {@link HttpURLConnection}
+ *
+ * @throws IOException
+ */
+ @VisibleForTesting
+ protected HttpURLConnection createConnection(URL url) throws IOException
+ {
+ return (HttpURLConnection) url.openConnection();
+ }
+
+ /**
+ * Creates a new proxy {@link HttpURLConnection} from the given {@link URL}
+ * and {@link SocketAddress}. The method is visible for testing.
+ *
+ *
+ * @param url url
+ * @param address proxy socket address
+ *
+ * @return new proxy {@link HttpURLConnection}
+ *
+ * @throws IOException
+ */
+ @VisibleForTesting
+ protected HttpURLConnection createProxyConnecton(URL url,
+ SocketAddress address)
+ throws IOException
+ {
+ return (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP,
+ address));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected ContentTransformer createTransformer(Class> type, String contentType)
+ {
+ ContentTransformer responsible = null;
+
+ for (ContentTransformer transformer : contentTransformers)
+ {
+ if (transformer.isResponsible(type, contentType))
+ {
+ responsible = transformer;
+
+ break;
+ }
+ }
+
+ if (responsible == null)
+ {
+ throw new ContentTransformerNotFoundException(
+ "could not find content transformer for content type ".concat(
+ contentType));
+ }
+
+ return responsible;
+ }
+
+ /**
+ * Executes the given request and returns the server response.
+ *
+ *
+ * @param request http request
+ *
+ * @return server response
+ *
+ * @throws IOException
+ */
+ @Override
+ protected AdvancedHttpResponse request(BaseHttpRequest> request)
+ throws IOException
+ {
+ HttpURLConnection connection = openConnection(request,
+ new URL(request.getUrl()));
+
+ applyBaseSettings(request, connection);
+
+ if (connection instanceof HttpsURLConnection)
+ {
+ applySSLSettings(request, (HttpsURLConnection) connection);
+ }
+
+ Content content = null;
+
+ if (request instanceof AdvancedHttpRequestWithBody)
+ {
+ AdvancedHttpRequestWithBody ahrwb = (AdvancedHttpRequestWithBody) request;
+
+ content = ahrwb.getContent();
+
+ if (content != null)
+ {
+ content.prepare(ahrwb);
+ }
+ else
+ {
+ request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
+ }
+ }
+ else
+ {
+ request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
+ }
+
+ applyHeaders(request, connection);
+
+ if (content != null)
+ {
+ applyContent(connection, content);
+ }
+
+ return new DefaultAdvancedHttpResponse(this, connection,
+ connection.getResponseCode(), connection.getResponseMessage());
+ }
+
+ private void appendProxyAuthentication(HttpURLConnection connection)
+ {
+ String username = configuration.getProxyUser();
+ String password = configuration.getProxyPassword();
+
+ if (!Strings.isNullOrEmpty(username) ||!Strings.isNullOrEmpty(password))
+ {
+ logger.debug("append proxy authentication header for user {}", username);
+
+ String auth = username.concat(CREDENTIAL_SEPARATOR).concat(password);
+
+ auth = Base64.encodeToString(auth.getBytes());
+ connection.addRequestProperty(HEADER_PROXY_AUTHORIZATION,
+ PREFIX_BASIC_AUTHENTICATION.concat(auth));
+ }
+ }
+
+ private void applyBaseSettings(BaseHttpRequest> request,
+ HttpURLConnection connection)
+ throws ProtocolException
+ {
+ connection.setRequestMethod(request.getMethod());
+ connection.setReadTimeout(TIMEOUT_RAED);
+ connection.setConnectTimeout(TIMEOUT_CONNECTION);
+ }
+
+ private void applyContent(HttpURLConnection connection, Content content)
+ throws IOException
+ {
+ connection.setDoOutput(true);
+
+ OutputStream output = null;
+
+ try
+ {
+ output = connection.getOutputStream();
+ content.process(output);
+ }
+ finally
+ {
+ Closeables.close(output, true);
+ }
+ }
+
+ private void applyHeaders(BaseHttpRequest> request,
+ HttpURLConnection connection)
+ {
+ Multimap headers = request.getHeaders();
+
+ for (String key : headers.keySet())
+ {
+ for (String value : headers.get(key))
+ {
+ connection.addRequestProperty(key, value);
+ }
+ }
+ }
+
+ private void applySSLSettings(BaseHttpRequest> request,
+ HttpsURLConnection connection)
+ {
+ if (request.isDisableCertificateValidation())
+ {
+ logger.trace("disable certificate validation");
+
+ try
+ {
+ TrustManager[] trustAllCerts = new TrustManager[] {
+ new TrustAllTrustManager() };
+ SSLContext sc = SSLContext.getInstance("SSL");
+
+ 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)
+ {
+ logger.error("could not disable certificate validation", ex);
+ }
+ }
+ else
+ {
+ logger.trace("set ssl socker factory from provider");
+ connection.setSSLSocketFactory(sslContextProvider.get().getSocketFactory());
+ }
+
+ if (request.isDisableHostnameValidation())
+ {
+ logger.trace("disable hostname validation");
+ connection.setHostnameVerifier(new TrustAllHostnameVerifier());
+ }
+ }
+
+ private HttpURLConnection openConnection(BaseHttpRequest> request, URL url)
+ throws IOException
+ {
+ HttpURLConnection connection;
+
+ if (isProxyEnabled(request))
+ {
+ connection = openProxyConnection(request, url);
+ appendProxyAuthentication(connection);
+ }
+ else
+ {
+ if (request.isIgnoreProxySettings())
+ {
+ logger.trace("ignore proxy settings");
+ }
+
+ logger.debug("fetch {}", url.toExternalForm());
+
+ connection = createConnection(url);
+ }
+
+ return connection;
+ }
+
+ private HttpURLConnection openProxyConnection(BaseHttpRequest> request,
+ URL url)
+ throws IOException
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("fetch '{}' using proxy {}:{}", url.toExternalForm(),
+ configuration.getProxyServer(), configuration.getProxyPort());
+ }
+
+ SocketAddress address =
+ new InetSocketAddress(configuration.getProxyServer(),
+ configuration.getProxyPort());
+
+ return createProxyConnecton(url, address);
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ private boolean isProxyEnabled(BaseHttpRequest> request)
+ {
+ return !request.isIgnoreProxySettings()
+ && Proxies.isEnabled(configuration, request.getUrl());
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** scm-manager main configuration */
+ private final ScmConfiguration configuration;
+
+ /** set of content transformers */
+ private final Set contentTransformers;
+
+ /** ssl context provider */
+ private final Provider sslContextProvider;
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java
new file mode 100644
index 0000000000..21179ce1b6
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java
@@ -0,0 +1,226 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.io.ByteSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.HttpURLConnection;
+
+import java.util.List;
+import java.util.Map.Entry;
+
+/**
+ * Http server response object of {@link DefaultAdvancedHttpClient}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse
+{
+
+ /**
+ * Constructs a new {@link DefaultAdvancedHttpResponse}.
+ *
+ * @param client ahc
+ * @param connection http connection
+ * @param status response status code
+ * @param statusText response status text
+ */
+ DefaultAdvancedHttpResponse(DefaultAdvancedHttpClient client,
+ HttpURLConnection connection, int status, String statusText)
+ {
+ this.client = client;
+ this.connection = connection;
+ this.status = status;
+ this.statusText = statusText;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteSource contentAsByteSource() throws IOException
+ {
+ return new URLConnectionByteSource(connection);
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Multimap getHeaders()
+ {
+ if (headers == null)
+ {
+ headers = LinkedHashMultimap.create();
+
+ for (Entry> e :
+ connection.getHeaderFields().entrySet())
+ {
+ headers.putAll(e.getKey(), e.getValue());
+ }
+ }
+
+ return headers;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getStatus()
+ {
+ return status;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getStatusText()
+ {
+ return statusText;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected ContentTransformer createTransformer(Class> type,
+ String contentType)
+ {
+ return client.createTransformer(type, contentType);
+ }
+
+ //~--- inner classes --------------------------------------------------------
+
+ /**
+ * {@link ByteSource} implementation of a http connection.
+ */
+ private static class URLConnectionByteSource extends ByteSource
+ {
+
+ /**
+ * the logger for URLConnectionByteSource
+ */
+ private static final Logger logger =
+ LoggerFactory.getLogger(URLConnectionByteSource.class);
+
+ //~--- constructors -------------------------------------------------------
+
+ /**
+ * Constructs a new {@link URLConnectionByteSource}.
+ *
+ *
+ * @param connection http connection
+ */
+ private URLConnectionByteSource(HttpURLConnection connection)
+ {
+ this.connection = connection;
+ }
+
+ //~--- methods ------------------------------------------------------------
+
+ /**
+ * Opens the input stream of http connection, if an error occurs during
+ * opening the method will return the error stream instead.
+ *
+ *
+ * @return input or error stream of http connection
+ *
+ * @throws IOException
+ */
+ @Override
+ public InputStream openStream() throws IOException
+ {
+ InputStream stream;
+
+ try
+ {
+ stream = connection.getInputStream();
+ }
+ catch (IOException ex)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "could not open input stream, open error stream instead", ex);
+ }
+
+ stream = connection.getErrorStream();
+ }
+
+ return stream;
+ }
+
+ //~--- fields -------------------------------------------------------------
+
+ /** http connection */
+ private final HttpURLConnection connection;
+ }
+
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** Field description */
+ private final DefaultAdvancedHttpClient client;
+
+ /** http connection */
+ private final HttpURLConnection connection;
+
+ /** server response status */
+ private final int status;
+
+ /** server response text */
+ private final String statusText;
+
+ /** http headers */
+ private Multimap headers;
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/JsonContentTransformer.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/JsonContentTransformer.java
new file mode 100644
index 0000000000..fce81ba386
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/JsonContentTransformer.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.io.ByteSource;
+
+import org.codehaus.jackson.map.AnnotationIntrospector;
+import org.codehaus.jackson.map.DeserializationConfig;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
+import org.codehaus.jackson.xc.JaxbAnnotationIntrospector;
+
+import sonia.scm.plugin.Extension;
+import sonia.scm.util.IOUtil;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.ws.rs.core.MediaType;
+
+/**
+ * {@link ContentTransformer} for json. The {@link JsonContentTransformer} uses
+ * jacksons {@link ObjectMapper} to marshalling/unmarshalling.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+@Extension
+public class JsonContentTransformer implements ContentTransformer
+{
+
+ /**
+ * Constructs a new {@link JsonContentTransformer}.
+ *
+ */
+ public JsonContentTransformer()
+ {
+ this.mapper = new ObjectMapper();
+
+ // allow jackson and jaxb annotations
+ AnnotationIntrospector jackson = new JacksonAnnotationIntrospector();
+ AnnotationIntrospector jaxb = new JaxbAnnotationIntrospector();
+
+ this.mapper.setAnnotationIntrospector(new AnnotationIntrospector.Pair(jackson, jaxb));
+
+ // do not fail on unknown json properties
+ this.mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteSource marshall(Object object)
+ {
+ ByteSource source = null;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ try
+ {
+ mapper.writeValue(baos, object);
+ source = ByteSource.wrap(baos.toByteArray());
+ }
+ catch (IOException ex)
+ {
+ throw new ContentTransformerException("could not marshall object", ex);
+ }
+
+ return source;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T unmarshall(Class type, ByteSource content)
+ {
+ T object = null;
+ InputStream stream = null;
+
+ try
+ {
+ stream = content.openBufferedStream();
+ object = mapper.readValue(stream, type);
+ }
+ catch (IOException ex)
+ {
+ throw new ContentTransformerException("could not unmarshall content", ex);
+ }
+ finally
+ {
+ IOUtil.close(stream);
+ }
+
+ return object;
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Returns {@code true}, if the content type is compatible with
+ * application/json.
+ *
+ *
+ * @param type object type
+ * @param contentType content type
+ *
+ * @return {@code true}, if the content type is compatible with
+ * application/json
+ */
+ @Override
+ public boolean isResponsible(Class> type, String contentType)
+ {
+ return MediaType.valueOf(contentType).isCompatible(MediaType.APPLICATION_JSON_TYPE);
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** object mapper */
+ private final ObjectMapper mapper;
+}
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
new file mode 100644
index 0000000000..84dcda3593
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+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 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;
+
+/**
+ * {@link ContentTransformer} for xml. The {@link XmlContentTransformer} uses
+ * jaxb to marshalling/unmarshalling.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+@Extension
+public class XmlContentTransformer implements ContentTransformer
+{
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteSource marshall(Object object)
+ {
+ ByteSource source = null;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ try
+ {
+ JAXB.marshal(object, baos);
+ source = ByteSource.wrap(baos.toByteArray());
+ }
+ catch (DataBindingException ex)
+ {
+ throw new ContentTransformerException("could not marshall object", ex);
+ }
+
+ return source;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T unmarshall(Class type, ByteSource content)
+ {
+ T object = null;
+ InputStream stream = null;
+
+ try
+ {
+ stream = content.openBufferedStream();
+ object = JAXB.unmarshal(stream, type);
+ }
+ catch (IOException ex)
+ {
+ throw new ContentTransformerException("could not unmarshall content", ex);
+ }
+ catch (DataBindingException ex)
+ {
+ throw new ContentTransformerException("could not unmarshall content", ex);
+ }
+ finally
+ {
+ IOUtil.close(stream);
+ }
+
+ return object;
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * Returns {@code true}, if the content type is compatible with
+ * application/xml.
+ *
+ *
+ * @param type object type
+ * @param contentType content type
+ *
+ * @return {@code true}, if the content type is compatible with
+ * application/xml
+ */
+ @Override
+ public boolean isResponsible(Class> type, String contentType)
+ {
+ return MediaType.valueOf(contentType).isCompatible(
+ MediaType.APPLICATION_XML_TYPE);
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java
index 3f793e3655..cc3ef01c56 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java
@@ -39,7 +39,6 @@ import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.collect.Iterables;
-import com.google.inject.Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 3771005ca7..ed1f691988 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
@@ -40,20 +40,17 @@ import com.github.legman.Subscribe;
import com.google.common.base.Predicate;
import com.google.common.io.Files;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.ConfigurationException;
import sonia.scm.SCMContextProvider;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.config.ScmConfigurationChangedEvent;
import sonia.scm.io.ZipUnArchiver;
-import sonia.scm.net.HttpClient;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.SystemUtil;
@@ -78,12 +75,12 @@ import java.util.Map;
import java.util.Set;
import javax.xml.bind.JAXB;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Unmarshaller;
+
+import sonia.scm.net.ahc.AdvancedHttpClient;
/**
- * TODO replace aether stuff
+ * TODO replace aether stuff.
+ * TODO check AdvancedPluginConfiguration from 1.x
*
* @author Sebastian Sdorra
*/
@@ -117,17 +114,17 @@ public class DefaultPluginManager implements PluginManager
* @param configuration
* @param pluginLoader
* @param cacheManager
- * @param clientProvider
+ * @param httpClient
*/
@Inject
public DefaultPluginManager(SCMContextProvider context,
ScmConfiguration configuration, PluginLoader pluginLoader,
- CacheManager cacheManager, Provider clientProvider)
+ CacheManager cacheManager, AdvancedHttpClient httpClient)
{
this.context = context;
this.configuration = configuration;
this.cache = cacheManager.getCache(CACHE_NAME);
- this.clientProvider = clientProvider;
+ this.httpClient = httpClient;
installedPlugins = new HashMap<>();
for (PluginWrapper wrapper : pluginLoader.getInstalledPlugins())
@@ -140,16 +137,6 @@ public class DefaultPluginManager implements PluginManager
installedPlugins.put(info.getId(), plugin);
}
}
-
- try
- {
- unmarshaller =
- JAXBContext.newInstance(PluginCenter.class).createUnmarshaller();
- }
- catch (JAXBException ex)
- {
- throw new ConfigurationException(ex);
- }
}
//~--- methods --------------------------------------------------------------
@@ -624,21 +611,9 @@ public class DefaultPluginManager implements PluginManager
*/
if (REMOTE_PLUGINS_ENABLED && Util.isNotEmpty(pluginUrl))
{
- InputStream input = null;
-
try
{
- input = clientProvider.get().get(pluginUrl).getContent();
-
- /*
- * TODO: add gzip support
- *
- * if (gzip)
- * {
- * input = new GZIPInputStream(input);
- * }
- */
- center = (PluginCenter) unmarshaller.unmarshal(input);
+ center = httpClient.get(pluginUrl).request().contentFromXml(PluginCenter.class);
preparePlugins(center);
cache.put(PluginCenter.class.getName(), center);
@@ -653,14 +628,10 @@ public class DefaultPluginManager implements PluginManager
* pluginHandler.setPluginRepositories(center.getRepositories());
*/
}
- catch (IOException | JAXBException ex)
+ catch (IOException ex)
{
logger.error("could not load plugins from plugin center", ex);
}
- finally
- {
- IOUtil.close(input);
- }
}
if (center == null)
@@ -758,7 +729,7 @@ public class DefaultPluginManager implements PluginManager
private final Cache cache;
/** Field description */
- private final Provider clientProvider;
+ private final AdvancedHttpClient httpClient;
/** Field description */
private final ScmConfiguration configuration;
@@ -768,7 +739,4 @@ public class DefaultPluginManager implements PluginManager
/** Field description */
private final Map installedPlugins;
-
- /** Field description */
- private Unmarshaller unmarshaller;
}
diff --git a/scm-webapp/src/main/java/sonia/scm/resources/AbstractResourceManager.java b/scm-webapp/src/main/java/sonia/scm/resources/AbstractResourceManager.java
index 1316d46dd1..95d7b39b69 100644
--- a/scm-webapp/src/main/java/sonia/scm/resources/AbstractResourceManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/resources/AbstractResourceManager.java
@@ -146,6 +146,9 @@ public abstract class AbstractResourceManager implements ResourceManager
processPlugin(resources, plugin.getPlugin());
}
}
+
+ // fix order of script resources, see https://goo.gl/ok03l4
+ Collections.sort(resources);
return resources;
}
diff --git a/scm-webapp/src/main/java/sonia/scm/schedule/InjectionEnabledJob.java b/scm-webapp/src/main/java/sonia/scm/schedule/InjectionEnabledJob.java
new file mode 100644
index 0000000000..44ac5ae3ab
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/schedule/InjectionEnabledJob.java
@@ -0,0 +1,90 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.schedule;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.web.security.AdministrationContext;
+import sonia.scm.web.security.PrivilegedAction;
+
+/**
+ * InjectionEnabledJob allows the execution of quartz jobs and enable injection on them.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.47
+ */
+public class InjectionEnabledJob implements Job {
+
+ private static final Logger logger = LoggerFactory.getLogger(InjectionEnabledJob.class);
+
+ @Override
+ public void execute(JobExecutionContext jec) throws JobExecutionException {
+ Preconditions.checkNotNull(jec, "execution context is null");
+
+ JobDetail detail = jec.getJobDetail();
+ Preconditions.checkNotNull(detail, "job detail not provided");
+
+ JobDataMap dataMap = detail.getJobDataMap();
+ Preconditions.checkNotNull(dataMap, "job detail does not contain data map");
+
+ Injector injector = (Injector) dataMap.get(Injector.class.getName());
+ Preconditions.checkNotNull(injector, "data map does not contain injector");
+
+ final Provider runnableProvider = (Provider) dataMap.get(Runnable.class.getName());
+ if (runnableProvider == null) {
+ throw new JobExecutionException("could not find runnable provider");
+ }
+
+ AdministrationContext ctx = injector.getInstance(AdministrationContext.class);
+ ctx.runAsAdmin(new PrivilegedAction()
+ {
+ @Override
+ public void run()
+ {
+ logger.trace("create runnable from provider");
+ Runnable runnable = runnableProvider.get();
+ logger.debug("execute injection enabled job {}", runnable.getClass());
+ runnable.run();
+ }
+ });
+ }
+
+
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/schedule/QuartzScheduler.java b/scm-webapp/src/main/java/sonia/scm/schedule/QuartzScheduler.java
new file mode 100644
index 0000000000..5b46d438d8
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/schedule/QuartzScheduler.java
@@ -0,0 +1,175 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.schedule;
+
+import com.google.common.base.Throwables;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import javax.inject.Inject;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.JobBuilder;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
+import org.quartz.impl.StdSchedulerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.Initable;
+import sonia.scm.SCMContextProvider;
+
+/**
+ * {@link Scheduler} which uses the quartz scheduler.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.47
+ *
+ * @see Quartz Job Scheduler
+ */
+@Singleton
+public class QuartzScheduler implements Scheduler, Initable {
+
+ private static final Logger logger = LoggerFactory.getLogger(QuartzScheduler.class);
+
+ private final Injector injector;
+ private final org.quartz.Scheduler scheduler;
+
+ /**
+ * Creates a new quartz scheduler.
+ *
+ * @param injector injector
+ */
+ @Inject
+ public QuartzScheduler(Injector injector)
+ {
+ this.injector = injector;
+
+ // get default scheduler
+ try {
+ scheduler = StdSchedulerFactory.getDefaultScheduler();
+ } catch (SchedulerException ex) {
+ throw Throwables.propagate(ex);
+ }
+ }
+
+ /**
+ * Creates a new quartz scheduler. This constructor is only for testing.
+ *
+ * @param injector injector
+ * @param scheduler quartz scheduler
+ */
+ QuartzScheduler(Injector injector, org.quartz.Scheduler scheduler)
+ {
+ this.injector = injector;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public void init(SCMContextProvider context)
+ {
+ try
+ {
+ if (!scheduler.isStarted())
+ {
+ scheduler.start();
+ }
+ }
+ catch (SchedulerException ex)
+ {
+ logger.error("can not start scheduler", ex);
+ }
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ try
+ {
+ if (scheduler.isStarted()){
+ scheduler.shutdown();
+ }
+ }
+ catch (SchedulerException ex)
+ {
+ logger.error("can not stop scheduler", ex);
+ }
+ }
+
+ @Override
+ public Task schedule(String expression, final Runnable runnable)
+ {
+ return schedule(expression, new Provider(){
+ @Override
+ public Runnable get()
+ {
+ return runnable;
+ }
+ });
+ }
+
+ @Override
+ public Task schedule(String expression, Class extends Runnable> runnable)
+ {
+ return schedule(expression, injector.getProvider(runnable));
+ }
+
+ private Task schedule(String expression, Provider extends Runnable> provider){
+ // create data map with injection provider for InjectionEnabledJob
+ JobDataMap map = new JobDataMap();
+ map.put(Runnable.class.getName(), provider);
+ map.put(Injector.class.getName(), injector);
+
+ // create job detail for InjectionEnabledJob with the provider for the annotated class
+ JobDetail detail = JobBuilder.newJob(InjectionEnabledJob.class)
+ .usingJobData(map)
+ .build();
+
+ // create a trigger with the cron expression from the annotation
+ Trigger trigger = TriggerBuilder.newTrigger()
+ .forJob(detail)
+ .withSchedule(CronScheduleBuilder.cronSchedule(expression))
+ .build();
+
+ try {
+ scheduler.scheduleJob(detail, trigger);
+ } catch (SchedulerException ex) {
+ throw Throwables.propagate(ex);
+ }
+
+ return new QuartzTask(scheduler, trigger.getJobKey());
+ }
+
+
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/schedule/QuartzTask.java b/scm-webapp/src/main/java/sonia/scm/schedule/QuartzTask.java
new file mode 100644
index 0000000000..a45790a2e9
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/schedule/QuartzTask.java
@@ -0,0 +1,68 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.schedule;
+
+import com.google.common.base.Throwables;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+
+/**
+ * Task implementation for the {@link QuartzScheduler}.
+ *
+ * @author Sebastian Sdorra
+ */
+public class QuartzTask implements Task {
+
+ private final org.quartz.Scheduler scheduler;
+ private final JobKey jobKey;
+
+ QuartzTask(Scheduler scheduler, JobKey jobKey)
+ {
+ this.scheduler = scheduler;
+ this.jobKey = jobKey;
+ }
+
+ @Override
+ public void cancel()
+ {
+ try
+ {
+ scheduler.deleteJob(jobKey);
+ }
+ catch (SchedulerException ex)
+ {
+ throw Throwables.propagate(ex);
+ }
+ }
+
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
index 3ce787e2b4..e3826ad853 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
@@ -70,6 +70,10 @@ import sonia.scm.util.Util;
import java.util.List;
import java.util.Set;
+import sonia.scm.group.Group;
+import sonia.scm.group.GroupModificationEvent;
+import sonia.scm.repository.RepositoryModificationEvent;
+import sonia.scm.user.UserModificationEvent;
/**
*
@@ -80,6 +84,9 @@ import java.util.Set;
public class DefaultAuthorizationCollector implements AuthorizationCollector
{
+ // TODO move to util class
+ private static final String SEPARATOR = System.getProperty("line.separator", "\n");
+
/** Field description */
private static final String ADMIN_PERMISSION = "*";
@@ -139,10 +146,15 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
}
/**
- * Method description
+ * Invalidates the cache of a user which was modified. The cache entries for the user will be invalidated for the
+ * following reasons:
+ *
+ *
Admin or Active flag was modified.
+ *
New user created, for the case of old cache values
+ *
User deleted
+ *
*
- *
- * @param event
+ * @param event user event
*/
@Subscribe
public void onEvent(UserEvent event)
@@ -150,82 +162,160 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
if (event.getEventType().isPost())
{
User user = event.getItem();
-
- if (logger.isDebugEnabled())
+ String username = user.getId();
+ if (event instanceof UserModificationEvent)
{
- logger.debug(
- "clear cache of user {}, because user properties have changed",
- user.getName());
+ User beforeModification = ((UserModificationEvent) event).getItemBeforeModification();
+ if (shouldCacheBeCleared(user, beforeModification))
+ {
+ logger.debug("invalidate cache of user {}, because of a permission relevant field has changed", username);
+ invalidateUserCache(username);
+ }
+ else
+ {
+ logger.debug("cache of user {} is not invalidated, because no permission relevant field has changed", username);
+ }
+ }
+ else
+ {
+ logger.debug("invalidate cache of user {}, because of user {} event", username, event.getEventType());
+ invalidateUserCache(username);
}
-
- // check if this is neccessary
- cache.clear();
}
}
+ private boolean shouldCacheBeCleared(User user, User beforeModification)
+ {
+ return user.isAdmin() != beforeModification.isAdmin() || user.isActive() != beforeModification.isActive();
+ }
+
+ private void invalidateUserCache(final String username)
+ {
+ cache.removeAll((CacheKey item) -> username.equalsIgnoreCase(item.username));
+ }
+
/**
- * Method description
+ * Invalidates the whole cache, if a repository has changed. The cache get cleared for one of the following reasons:
+ *
+ *
New repository created
+ *
Repository was removed
+ *
Archived, Public readable or permission field of the repository was modified
+ *
*
- *
- * @param event
+ * @param event repository event
*/
@Subscribe
public void onEvent(RepositoryEvent event)
{
if (event.getEventType().isPost())
{
- if (logger.isDebugEnabled())
+ Repository repository = event.getItem();
+
+ if (event instanceof RepositoryModificationEvent)
{
- logger.debug("clear cache, because repository {} has changed",
- event.getItem().getName());
+ Repository beforeModification = ((RepositoryModificationEvent) event).getItemBeforeModification();
+ if (shouldCacheBeCleared(repository, beforeModification))
+ {
+ logger.debug("clear cache, because a relevant field of repository {} has changed", repository.getName());
+ cache.clear();
+ }
+ else
+ {
+ logger.debug(
+ "cache is not invalidated, because non relevant field of repository {} has changed",
+ repository.getName()
+ );
+ }
+ }
+ else
+ {
+ logger.debug("clear cache, received {} event of repository {}", event.getEventType(), repository.getName());
+ cache.clear();
}
-
- cache.clear();
}
}
+ private boolean shouldCacheBeCleared(Repository repository, Repository beforeModification)
+ {
+ return repository.isArchived() != beforeModification.isArchived()
+ || repository.isPublicReadable() != beforeModification.isPublicReadable()
+ || ! repository.getPermissions().equals(beforeModification.getPermissions());
+ }
+
/**
- * Method description
+ * Invalidates the whole cache if a group permission has changed and invalidates the cached entries of a user, if a
+ * user permission has changed.
*
*
- * @param event
+ * @param event permission event
*/
@Subscribe
public void onEvent(StoredAssignedPermissionEvent event)
{
if (event.getEventType().isPost())
{
- if (logger.isDebugEnabled())
+ StoredAssignedPermission permission = event.getPermission();
+ if (permission.isGroupPermission())
{
- logger.debug("clear cache, because permission {} has changed",
- event.getPermission().getId());
+ logger.debug("clear cache, because global group permission {} has changed", permission.getId());
+ cache.clear();
+ }
+ else
+ {
+ logger.debug(
+ "clear cache of user {}, because permission {} has changed",
+ permission.getName(), event.getPermission().getId()
+ );
+ invalidateUserCache(permission.getName());
}
-
- cache.clear();
}
}
/**
- * Method description
+ * Invalidates the whole cache, if a group has changed. The cache get cleared for one of the following reasons:
+ *
+ *
New group created
+ *
Group was removed
+ *
Group members was modified
+ *
*
- *
- * @param event
+ * @param event group event
*/
@Subscribe
public void onEvent(GroupEvent event)
{
if (event.getEventType().isPost())
{
- if (logger.isDebugEnabled())
+ Group group = event.getItem();
+ if (event instanceof GroupModificationEvent)
{
- logger.debug("clear cache, because group {} has changed",
- event.getItem().getId());
+ Group beforeModification = ((GroupModificationEvent) event).getItemBeforeModification();
+ if (shouldCacheBeCleared(group, beforeModification))
+ {
+ logger.debug("clear cache, because group {} has changed", group.getId());
+ cache.clear();
+ }
+ else
+ {
+ logger.debug(
+ "cache is not invalidated, because non relevant field of group {} has changed",
+ group.getId()
+ );
+ }
+ }
+ else
+ {
+ logger.debug("clear cache, received group event {} for group {}", event.getEventType(), group.getId());
+ cache.clear();
}
-
- cache.clear();
}
}
+ private boolean shouldCacheBeCleared(Group group, Group beforeModification)
+ {
+ return !group.getMembers().equals(beforeModification.getMembers());
+ }
+
/**
* Method description
*
@@ -251,18 +341,13 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
if (info == null)
{
- if (logger.isTraceEnabled())
- {
- logger.trace("collect AuthorizationInfo for user {}", user.getName());
- }
-
+ logger.trace("collect AuthorizationInfo for user {}", user.getName());
info = createAuthorizationInfo(user, groupNames);
cache.put(cacheKey, info);
}
else if (logger.isTraceEnabled())
{
- logger.trace("retrieve AuthorizationInfo for user {} from cache",
- user.getName());
+ logger.trace("retrieve AuthorizationInfo for user {} from cache", user.getName());
}
return info;
@@ -271,21 +356,8 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
private void collectGlobalPermissions(Builder builder,
final User user, final GroupNames groups)
{
- if (logger.isTraceEnabled())
- {
- logger.trace("collect global permissions for user {}", user.getName());
- }
-
List globalPermissions =
- securitySystem.getPermissions(new Predicate()
- {
-
- @Override
- public boolean apply(AssignedPermission input)
- {
- return isUserPermission(user, groups, input);
- }
- });
+ securitySystem.getPermissions((AssignedPermission input) -> isUserPermitted(user, groups, input));
for (StoredAssignedPermission gp : globalPermissions)
{
@@ -301,12 +373,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
{
for (Repository repository : repositoryDAO.getAll())
{
- if (logger.isTraceEnabled())
- {
- logger.trace("collect permissions for repository {} and user {}",
- repository.getName(), user.getName());
- }
-
collectRepositoryPermissions(builder, repository, user, groups);
}
}
@@ -314,30 +380,36 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
private void collectRepositoryPermissions(Builder builder,
Repository repository, User user, GroupNames groups)
{
- List repositoryPermissions =
- repository.getPermissions();
+ List repositoryPermissions
+ = repository.getPermissions();
if (Util.isNotEmpty(repositoryPermissions))
{
-
+ boolean hasPermission = false;
for (sonia.scm.repository.Permission permission : repositoryPermissions)
{
- if (isUserPermission(user, groups, permission))
+ if (isUserPermitted(user, groups, permission))
{
- String perm = permission.getType().getPermissionPrefix().concat(
- repository.getId());
-
- logger.trace("add repository permission {} for user {}", perm,
- user.getName());
+ String perm = permission.getType().getPermissionPrefix().concat(repository.getId());
+ if (logger.isTraceEnabled())
+ {
+ logger.trace("add repository permission {} for user {} at repository {}",
+ perm, user.getName(), repository.getName());
+ }
builder.add(perm);
}
}
+
+ if (!hasPermission && logger.isTraceEnabled())
+ {
+ logger.trace("no permission for user {} defined at repository {}", user.getName(), repository.getName());
+ }
}
else if (logger.isTraceEnabled())
{
- logger.trace("repository {} has not permission entries",
+ logger.trace("repository {} has no permission entries",
repository.getName());
}
}
@@ -371,19 +443,47 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
-
info.addStringPermissions(permissions);
-
+
+ if (logger.isTraceEnabled()){
+ logger.trace(createAuthorizationSummary(user, groups, info));
+ }
+
return info;
}
+
+ private String createAuthorizationSummary(User user, GroupNames groups, AuthorizationInfo authzInfo)
+ {
+ StringBuilder buffer = new StringBuilder("authorization summary: ");
+
+ buffer.append(SEPARATOR).append("username : ").append(user.getName());
+ buffer.append(SEPARATOR).append("groups : ");
+ append(buffer, groups);
+ buffer.append(SEPARATOR).append("roles : ");
+ append(buffer, authzInfo.getRoles());
+ buffer.append(SEPARATOR).append("permissions:");
+ append(buffer, authzInfo.getStringPermissions());
+ append(buffer, authzInfo.getObjectPermissions());
+
+ return buffer.toString();
+ }
+
+ private void append(StringBuilder buffer, Iterable> iterable){
+ if (iterable != null){
+ for ( Object item : iterable )
+ {
+ buffer.append(SEPARATOR).append(" - ").append(item);
+ }
+ }
+ }
//~--- get methods ----------------------------------------------------------
- private boolean isUserPermission(User user, GroupNames groups,
+ private boolean isUserPermitted(User user, GroupNames groups,
PermissionObject perm)
{
//J-
- return (perm.isGroupPermission() && groups.contains(perm.getName()))
+ return (perm.isGroupPermission() && groups.contains(perm.getName()))
|| ((!perm.isGroupPermission()) && user.getName().equals(perm.getName()));
//J+
}
@@ -443,7 +543,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
private final String username;
}
-
//~--- fields ---------------------------------------------------------------
/** authorization cache */
diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java
new file mode 100644
index 0000000000..de3c26995a
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.security;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Util methods to handle XsrfCookies.
+ *
+ * @author Sebastian Sdorra
+ * @version 1.47
+ */
+public final class XsrfCookies
+{
+
+ private XsrfCookies()
+ {
+ }
+
+ /**
+ * Creates a new xsrf protection cookie and add it to the response.
+ *
+ * @param request http servlet request
+ * @param response http servlet response
+ * @param token xsrf token
+ */
+ public static void create(HttpServletRequest request, HttpServletResponse response, String token){
+ applyCookie(request, response, new Cookie(XsrfProtectionFilter.KEY, token));
+
+ }
+
+ /**
+ * Removes the current xsrf protection cookie from response.
+ *
+ * @param request http servlet request
+ * @param response http servlet response
+ */
+ public static void remove(HttpServletRequest request, HttpServletResponse response)
+ {
+ Cookie[] cookies = request.getCookies();
+ if ( cookies != null ){
+ for ( Cookie c : cookies ){
+ if ( XsrfProtectionFilter.KEY.equals(c.getName()) ){
+ c.setMaxAge(0);
+ c.setValue(null);
+ applyCookie(request, response, c);
+ }
+ }
+ }
+ }
+
+ private static void applyCookie(HttpServletRequest request, HttpServletResponse response, Cookie cookie){
+ cookie.setPath(request.getContextPath());
+ response.addCookie(cookie);
+ }
+
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfProtectionFilter.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfProtectionFilter.java
new file mode 100644
index 0000000000..7d1a12f9f1
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfProtectionFilter.java
@@ -0,0 +1,140 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.security;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.UUID;
+import javax.inject.Singleton;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.Priority;
+import sonia.scm.config.ScmConfiguration;
+import sonia.scm.filter.Filters;
+import sonia.scm.filter.WebElement;
+import sonia.scm.util.HttpUtil;
+import sonia.scm.web.filter.HttpFilter;
+
+/**
+ * Xsrf protection http filter. The filter will issue an cookie with an xsrf protection token on the first ajax request
+ * of the scm web interface and marks the http session as xsrf protected. On every other request within a protected
+ * session, the web interface has to send the token from the cookie as http header on every request. If the filter
+ * receives an request to a protected session, without proper xsrf header the filter will abort the request and send an
+ * http error code back to the client. If the filter receives an request to a non protected session, from a non web
+ * interface client the filter will call the chain. The {@link XsrfProtectionFilter} is disabled by default and can be
+ * enabled with {@link ScmConfiguration#setEnabledXsrfProtection(boolean)}.
+ *
+ * TODO for scm-manager 2 we have to store the csrf token as part of the jwt token instead of session.
+ *
+ * @see https://bitbucket.org/sdorra/scm-manager/issues/793/json-hijacking-vulnerability-cwe-116-cwe
+ * @author Sebastian Sdorra
+ * @version 1.47
+ */
+@WebElement(Filters.PATTERN_RESTAPI)
+@Priority(Filters.PRIORITY_PRE_BASEURL)
+public final class XsrfProtectionFilter extends HttpFilter
+{
+
+ /**
+ * the logger for XsrfProtectionFilter
+ */
+ private static final Logger logger = LoggerFactory.getLogger(XsrfProtectionFilter.class);
+
+ /**
+ * Key used for session, header and cookie.
+ */
+ static final String KEY = "X-XSRF-Token";
+
+ @Inject
+ public XsrfProtectionFilter(ScmConfiguration configuration)
+ {
+ this.configuration = configuration;
+ }
+
+ @Override
+ protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws
+ IOException, ServletException
+ {
+ if (configuration.isEnabledXsrfProtection())
+ {
+ doXsrfProtection(request, response, chain);
+ }
+ else
+ {
+ logger.trace("xsrf protection is disabled, skipping check");
+ chain.doFilter(request, response);
+ }
+ }
+
+ private void doXsrfProtection(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws
+ IOException, ServletException
+ {
+ HttpSession session = request.getSession(true);
+ String storedToken = (String) session.getAttribute(KEY);
+ if ( ! Strings.isNullOrEmpty(storedToken) ){
+ String headerToken = request.getHeader(KEY);
+ if ( storedToken.equals(headerToken) ){
+ logger.trace("received valid xsrf protected request");
+ chain.doFilter(request, response);
+ } else {
+ // is forbidden the correct status code?
+ logger.warn("received request to a xsrf protected session without proper xsrf token");
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+ } else if (HttpUtil.isWUIRequest(request)) {
+ logger.debug("received wui request, mark session as xsrf protected and issue a new token");
+ String token = createToken();
+ session.setAttribute(KEY, token);
+ XsrfCookies.create(request, response, token);
+ chain.doFilter(request, response);
+ } else {
+ // handle non webinterface clients, which does not need xsrf protection
+ logger.trace("received request to a non xsrf protected session");
+ chain.doFilter(request, response);
+ }
+ }
+
+ private String createToken()
+ {
+ // TODO create interface and use a better method
+ return UUID.randomUUID().toString();
+ }
+
+ private ScmConfiguration configuration;
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/template/ErrorServlet.java b/scm-webapp/src/main/java/sonia/scm/template/ErrorServlet.java
index 8604324769..4d62b823a1 100644
--- a/scm-webapp/src/main/java/sonia/scm/template/ErrorServlet.java
+++ b/scm-webapp/src/main/java/sonia/scm/template/ErrorServlet.java
@@ -184,8 +184,8 @@ public class ErrorServlet extends HttpServlet
//~--- fields ---------------------------------------------------------------
/** Field description */
- private SCMContextProvider context;
+ private final SCMContextProvider context;
/** Field description */
- private TemplateEngineFactory templateEngineFactory;
+ private final TemplateEngineFactory templateEngineFactory;
}
diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java
index 89048efeaa..99e3abbe76 100644
--- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java
@@ -237,15 +237,14 @@ public class DefaultUserManager extends AbstractUserManager
}
UserPermissions.modify(user).check();
- User oldUser = userDAO.get(name);
-
- if (oldUser != null)
+ User notModified = userDAO.get(name);
+ if (notModified != null)
{
AssertUtil.assertIsValid(user);
+ fireEvent(HandlerEventType.BEFORE_MODIFY, user, notModified);
user.setLastModified(System.currentTimeMillis());
- fireEvent(HandlerEventType.BEFORE_MODIFY, user, oldUser);
userDAO.modify(user);
- fireEvent(HandlerEventType.MODIFY, user, oldUser);
+ fireEvent(HandlerEventType.MODIFY, user, notModified);
}
else
{
diff --git a/scm-webapp/src/main/java/sonia/scm/web/BasicWebTokenGenerator.java b/scm-webapp/src/main/java/sonia/scm/web/BasicWebTokenGenerator.java
index 80aee62b36..5f47ba371f 100644
--- a/scm-webapp/src/main/java/sonia/scm/web/BasicWebTokenGenerator.java
+++ b/scm-webapp/src/main/java/sonia/scm/web/BasicWebTokenGenerator.java
@@ -33,6 +33,12 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
+import com.google.common.base.Charsets;
+import com.google.inject.Inject;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.codec.Base64;
@@ -59,15 +65,28 @@ public class BasicWebTokenGenerator extends SchemeBasedWebTokenGenerator
{
/** credential separator for basic authentication */
- public static final String CREDENTIAL_SEPARATOR = ":";
+ private static final String CREDENTIAL_SEPARATOR = ":";
+ /** default encoding to decode basic authentication header */
+ private static final Charset DEFAULT_ENCODING = Charsets.ISO_8859_1;
+
/**
* the logger for BasicWebTokenGenerator
*/
private static final Logger logger =
LoggerFactory.getLogger(BasicWebTokenGenerator.class);
- //~--- methods --------------------------------------------------------------
+ private final UserAgentParser userAgentParser;
+
+ /**
+ * Constructs a new BasicWebTokenGenerator.
+ *
+ * @param userAgentParser parser for user-agent header
+ */
+ @Inject
+ public BasicWebTokenGenerator(UserAgentParser userAgentParser) {
+ this.userAgentParser = userAgentParser;
+ }
/**
* Creates a {@link UsernamePasswordToken} from an authorization header with
@@ -88,7 +107,7 @@ public class BasicWebTokenGenerator extends SchemeBasedWebTokenGenerator
if (HttpUtil.AUTHORIZATION_SCHEME_BASIC.equalsIgnoreCase(scheme))
{
- String token = new String(Base64.decode(authorization.getBytes()));
+ String token = decodeAuthenticationHeader(request, authorization);
int index = token.indexOf(CREDENTIAL_SEPARATOR);
@@ -115,4 +134,32 @@ public class BasicWebTokenGenerator extends SchemeBasedWebTokenGenerator
return authToken;
}
+
+/**
+ * Decode base64 of the basic authentication header. The method will use
+ * the charset provided by the {@link UserAgent}, if the
+ * {@link UserAgentParser} is not available the method will be fall back to
+ * ISO-8859-1.
+ *
+ * @param request http request
+ * @param authentication base64 encoded basic authentication string
+ *
+ * @return decoded basic authentication header
+ *
+ * @see issue 627
+ * @see Stackoverflow Basic Authentication
+ *
+ * @throws UnsupportedEncodingException
+ */
+ private String decodeAuthenticationHeader(HttpServletRequest request, String authentication)
+ {
+ Charset encoding = DEFAULT_ENCODING;
+
+ if (userAgentParser != null)
+ {
+ encoding = userAgentParser.parse(request).getBasicAuthenticationCharset();
+ }
+
+ return new String(Base64.decode(authentication), encoding);
+ }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java b/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java
new file mode 100644
index 0000000000..edf1f14ee3
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.web;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+
+import sonia.scm.plugin.Extension;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@Extension
+public class BrowserUserAgentProvider implements UserAgentProvider
+{
+
+ /** Field description */
+ @VisibleForTesting
+ static final UserAgent CHROME = UserAgent.builder(
+ "Chrome").basicAuthenticationCharset(
+ Charsets.UTF_8).build();
+
+ /** Field description */
+ private static final String CHROME_PATTERN = "chrome";
+
+ /** Field description */
+ @VisibleForTesting
+ static final UserAgent FIREFOX = UserAgent.builder("Firefox").build();
+
+ /** Field description */
+ private static final String FIREFOX_PATTERN = "firefox";
+
+ /** Field description */
+ @VisibleForTesting
+ static final UserAgent MSIE = UserAgent.builder("Internet Explorer").build();
+
+ /** Field description */
+ private static final String MSIE_PATTERN = "msie";
+
+ /** Field description */
+ @VisibleForTesting // todo check charset
+ static final UserAgent SAFARI = UserAgent.builder("Safari").build();
+
+ /** Field description */
+ private static final String OPERA_PATTERN = "opera";
+
+ /** Field description */
+ private static final String SAFARI_PATTERN = "safari";
+
+ /** Field description */
+ @VisibleForTesting // todo check charset
+ static final UserAgent OPERA = UserAgent.builder(
+ "Opera").basicAuthenticationCharset(
+ Charsets.UTF_8).build();
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Method description
+ *
+ *
+ * @param userAgentString
+ *
+ * @return
+ */
+ @Override
+ public UserAgent parseUserAgent(String userAgentString)
+ {
+ UserAgent ua = null;
+
+ if (userAgentString.contains(CHROME_PATTERN))
+ {
+ ua = CHROME;
+ }
+ else if (userAgentString.contains(FIREFOX_PATTERN))
+ {
+ ua = FIREFOX;
+ }
+ else if (userAgentString.contains(OPERA_PATTERN))
+ {
+ ua = OPERA;
+ }
+ else if (userAgentString.contains(MSIE_PATTERN))
+ {
+ ua = MSIE;
+ }
+ else if (userAgentString.contains(SAFARI_PATTERN))
+ {
+ ua = SAFARI;
+ }
+
+ return ua;
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java
index 52eead7956..3eaa684080 100644
--- a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java
+++ b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java
@@ -58,6 +58,7 @@ import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Map;
+import java.util.concurrent.ExecutorService;
import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
@@ -94,15 +95,17 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
* Constructs ...
*
*
+ * @param executor to handle error stream processing
* @param configuration
* @param context
* @param request
* @param response
*/
- public DefaultCGIExecutor(ScmConfiguration configuration,
- ServletContext context, HttpServletRequest request,
- HttpServletResponse response)
+ public DefaultCGIExecutor(ExecutorService executor,
+ ScmConfiguration configuration, ServletContext context,
+ HttpServletRequest request, HttpServletResponse response)
{
+ this.executor = executor;
this.configuration = configuration;
this.context = context;
this.request = request;
@@ -190,7 +193,7 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), workDirectory);
execute(p);
}
- catch (Throwable ex)
+ catch (IOException ex)
{
getExceptionHandler().handleException(request, response, ex);
}
@@ -507,7 +510,7 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
*/
private void processErrorStreamAsync(final Process process)
{
- new Thread(new Runnable()
+ executor.execute(new Runnable()
{
@Override
public void run()
@@ -528,7 +531,7 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
IOUtil.close(errorStream);
}
}
- }).start();
+ });
}
/**
@@ -539,6 +542,8 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
*/
private void processServletInput(Process process)
{
+ logger.trace("process servlet input");
+
OutputStream processOS = null;
ServletInputStream servletIS = null;
@@ -637,6 +642,9 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
//~--- fields ---------------------------------------------------------------
+ /** executor to handle error stream processing */
+ private final ExecutorService executor;
+
/** Field description */
private ScmConfiguration configuration;
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 eaa89224e2..8da159f3c0 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
@@ -35,10 +35,15 @@ package sonia.scm.web.cgi;
//~--- non-JDK imports --------------------------------------------------------
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
import sonia.scm.config.ScmConfiguration;
//~--- JDK imports ------------------------------------------------------------
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -50,6 +55,21 @@ import javax.servlet.http.HttpServletResponse;
public class DefaultCGIExecutorFactory implements CGIExecutorFactory
{
+ /**
+ * Constructs ...
+ *
+ */
+ public DefaultCGIExecutorFactory()
+ {
+ //J-
+ this.executor = Executors.newCachedThreadPool(
+ new ThreadFactoryBuilder().setNameFormat("cgi-pool-%d").build()
+ );
+ //J+
+ }
+
+ //~--- methods --------------------------------------------------------------
+
/**
* Method description
*
@@ -63,10 +83,15 @@ public class DefaultCGIExecutorFactory implements CGIExecutorFactory
*/
@Override
public CGIExecutor createExecutor(ScmConfiguration configuration,
- ServletContext context,
- HttpServletRequest request,
- HttpServletResponse response)
+ ServletContext context, HttpServletRequest request,
+ HttpServletResponse response)
{
- return new DefaultCGIExecutor(configuration, context, request, response);
+ return new DefaultCGIExecutor(executor, configuration, context, request,
+ response);
}
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** Field description */
+ private final ExecutorService executor;
}
diff --git a/scm-webapp/src/main/resources/logback.default.xml b/scm-webapp/src/main/resources/logback.default.xml
index 6ddedcdfad..939ff92b37 100644
--- a/scm-webapp/src/main/resources/logback.default.xml
+++ b/scm-webapp/src/main/resources/logback.default.xml
@@ -52,12 +52,19 @@
+
+
+
+
+
+
+
diff --git a/scm-webapp/src/main/webapp/index.mustache b/scm-webapp/src/main/webapp/index.mustache
index 4c2c3c913c..fec01e96ca 100644
--- a/scm-webapp/src/main/webapp/index.mustache
+++ b/scm-webapp/src/main/webapp/index.mustache
@@ -115,6 +115,8 @@
+
+
diff --git a/scm-webapp/src/main/webapp/resources/css/style.css b/scm-webapp/src/main/webapp/resources/css/style.css
index 355e8afcbe..deb968f35e 100644
--- a/scm-webapp/src/main/webapp/resources/css/style.css
+++ b/scm-webapp/src/main/webapp/resources/css/style.css
@@ -153,7 +153,7 @@ a.scm-link:hover {
}
.scm-commit {
- margin-bottom: 50px;
+ margin-bottom: 65px;
}
.scm-commit h1 {
diff --git a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js
index 29efa3fa00..a985688a3e 100644
--- a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js
+++ b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js
@@ -102,6 +102,10 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{
forceBaseUrlHelpText: 'Redirects to the base url if the request comes from a other url',
disableGroupingGridHelpText: 'Disable repository Groups. A complete page reload is required after a change of this value.',
enableRepositoryArchiveHelpText: 'Enable repository archives. A complete page reload is required after a change of this value.',
+
+ // TODO i18n
+ enableXsrfProtectionText: 'Enable Xsrf Protection',
+ enableXsrfProtectionHelpText: 'Enable Xsrf Cookie Protection. Note: This feature is still experimental.',
initComponent: function(){
@@ -166,6 +170,12 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{
name: 'skip-failed-authenticators',
inputValue: 'true',
helpText: this.skipFailedAuthenticatorsHelpText
+ },{
+ xtype: 'checkbox',
+ fieldLabel: this.enableXsrfProtectionText,
+ name: 'xsrf-protection',
+ inputValue: 'true',
+ helpText: this.enableXsrfProtectionHelpText
},{
xtype: 'numberfield',
fieldLabel: this.loginAttemptLimitText,
diff --git a/scm-webapp/src/main/webapp/resources/js/i18n/de.js b/scm-webapp/src/main/webapp/resources/js/i18n/de.js
index 91a76c7685..60597a2b89 100644
--- a/scm-webapp/src/main/webapp/resources/js/i18n/de.js
+++ b/scm-webapp/src/main/webapp/resources/js/i18n/de.js
@@ -221,7 +221,8 @@ if (Sonia.repository.Panel){
emptyText: 'Es wurde kein Repository selektiert',
removeTitleText: 'Repository entfernen',
- removeMsgText: 'Repository entfernen "{0}"?',
+ removeMsgText: 'Diese Aktion wird das Repository "{0}" permanent von der \n\
+ Festplatte löschen? Repository entfernen?',
errorTitleText: 'Fehler',
errorMsgText: 'Repository entfernen fehlgeschlagen'
});
diff --git a/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js b/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js
index 61abb54b88..2a66bfe8b0 100644
--- a/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js
+++ b/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js
@@ -71,6 +71,8 @@ Ext.override(Ext.form.Field, {
switch ( this.getXType() ){
case 'combo':
+ case 'repositoryBranchComboBox':
+ case 'repositoryTagComboBox':
if ( this.readOnly ){
cls = 'scm-form-help-button';
} else {
diff --git a/scm-webapp/src/main/webapp/resources/js/panel/sonia.panel.syntaxhighlighterpanel.js b/scm-webapp/src/main/webapp/resources/js/panel/sonia.panel.syntaxhighlighterpanel.js
index 8dcc40b176..fab21ac806 100644
--- a/scm-webapp/src/main/webapp/resources/js/panel/sonia.panel.syntaxhighlighterpanel.js
+++ b/scm-webapp/src/main/webapp/resources/js/panel/sonia.panel.syntaxhighlighterpanel.js
@@ -41,6 +41,7 @@ Sonia.panel.SyntaxHighlighterPanel = Ext.extend(Ext.Panel, {
fileName: 'shBrushAppleScript.js'
},{
name: 'Bash/shell',
+ brush: 'shell',
aliases: ['sh', 'bash', 'shell'],
fileName: 'shBrushBash.js'
},{
@@ -49,6 +50,7 @@ Sonia.panel.SyntaxHighlighterPanel = Ext.extend(Ext.Panel, {
fileName: 'shBrushColdFusion.js'
},{
name: 'C#',
+ brush: 'csharp',
aliases: ['cs', 'c-sharp', 'csharp'],
fileName: 'shBrushCSharp.js'
},{
@@ -97,6 +99,7 @@ Sonia.panel.SyntaxHighlighterPanel = Ext.extend(Ext.Panel, {
fileName: 'shBrushPhp.js'
},{
name: 'Plain Text',
+ brush: 'plain',
aliases: ['plain', 'text', 'txt', 'log'],
fileName: 'shBrushPlain.js'
},{
@@ -160,6 +163,9 @@ Sonia.panel.SyntaxHighlighterPanel = Ext.extend(Ext.Panel, {
for ( var j=0;j',
'Branches:', ' ',{
- xtype: 'combo',
- valueField: 'name',
- displayField: 'name',
- typeAhead: false,
- editable: false,
- triggerAction: 'all',
- store: branchStore,
+ xtype: 'repositoryBranchComboBox',
+ repositoryId: this.repository.id,
listeners: {
select: {
fn: this.selectBranch,
diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.commitpanel.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.commitpanel.js
index b711a477d3..7b72c4fca3 100644
--- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.commitpanel.js
+++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.commitpanel.js
@@ -73,10 +73,16 @@ Sonia.repository.CommitPanel = Ext.extend(Ext.Panel, {
',
templateModifications: '
\n\
+ \n\
+ ',
// header panel
commitPanel: null,
@@ -84,7 +90,15 @@ Sonia.repository.CommitPanel = Ext.extend(Ext.Panel, {
initComponent: function(){
this.commitPanel = new Ext.Panel({
- tpl: new Ext.XTemplate(this.templateCommit + this.templateModifications)
+ tpl: new Ext.XTemplate(this.templateCommit + this.templateModifications),
+ listeners: {
+ render: {
+ fn: function(panel){
+ panel.body.on('click', this.onClick, this);
+ },
+ scope: this
+ }
+ }
});
this.diffPanel = new Sonia.panel.SyntaxHighlighterPanel({
@@ -132,6 +146,20 @@ Sonia.repository.CommitPanel = Ext.extend(Ext.Panel, {
);
}
});
+ },
+
+ onClick: function(e){
+ var el = e.getTarget('a.scm-link');
+ if (el){
+ var path = el.rel;
+ if (path){
+ this.openFile(path);
+ }
+ }
+ },
+
+ openFile: function(path){
+ Sonia.repository.openFile(this.repository, path, this.revision);
}
});
diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.formpanel.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.formpanel.js
index d717a77daf..66482399ac 100644
--- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.formpanel.js
+++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.formpanel.js
@@ -61,8 +61,15 @@ Sonia.repository.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
Sonia.repository.FormPanel.superclass.initComponent.apply(this, arguments);
},
+ prepareUpdate: function(item) {
+
+ },
+
update: function(item){
item = Ext.apply( this.item, item );
+ // allow plugins to modify item
+ this.prepareUpdate(item);
+
if ( debug ){
console.debug( 'update repository: ' + item.name );
}
diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js
index 0003de58b0..fb66b91a5a 100644
--- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js
+++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js
@@ -82,7 +82,8 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
name: 'group',
convert: this.convertToGroup
},{
- name: 'name'
+ name: 'name',
+ sortType:'asUCString'
},{
name: 'type'
},{
@@ -431,21 +432,22 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
var infoPanel = main.getInfoPanel(item.type);
infoPanel.item = item;
+ var settingsForm = main.getSettingsForm(item.type);
+ settingsForm.item = item;
+ settingsForm.onUpdate = {
+ fn: this.reload,
+ scope: this
+ };
+ settingsForm.onCreate = {
+ fn: this.reload,
+ scope: this
+ };
+
var panels = [infoPanel];
if ( owner ){
- panels.push({
- item: item,
- xtype: 'repositorySettingsForm',
- onUpdate: {
- fn: this.reload,
- scope: this
- },
- onCreate: {
- fn: this.reload,
- scope: this
- }
- },{
+ panels.push(
+ settingsForm, {
item: item,
xtype: 'repositoryPermissionsForm',
listeners: {
@@ -481,8 +483,34 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
listener.call(listener.scope, item, panels);
}
});
+
+ // get the xtype of the currently opened tab
+ // search for the tab with the same xtype and activate it
+ // see issue https://goo.gl/3RGnA3
+ var activeTab = 0;
+ var activeXtype = this.getActiveTabXtype();
+ if (activeXtype){
+ for (var i=0; iDo you want to proceed?',
errorTitleText: 'Error',
errorMsgText: 'Repository deletion failed',
@@ -255,12 +256,12 @@ Sonia.repository.Panel = Ext.extend(Sonia.rest.Panel, {
return repository;
},
- executeRemoteCall: function(title, message, method, url, data, failureCallback){
+ executeRemoteCall: function(title, message, method, url, data, failureCallback, icon){
Ext.MessageBox.show({
title: title,
msg: message,
buttons: Ext.MessageBox.OKCANCEL,
- icon: Ext.MessageBox.QUESTION,
+ icon: icon ? icon : Ext.MessageBox.QUESTION,
fn: function(result){
if ( result === 'ok' ){
@@ -339,7 +340,8 @@ Sonia.repository.Panel = Ext.extend(Sonia.rest.Panel, {
this.errorTitleText,
this.errorMsgText
);
- }
+ },
+ Ext.MessageBox.WARNING
);
}
},
diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.repositorybrowser.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.repositorybrowser.js
index 25d40bbc9e..489dd3c06c 100644
--- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.repositorybrowser.js
+++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.repositorybrowser.js
@@ -154,27 +154,37 @@ Sonia.repository.RepositoryBrowser = Ext.extend(Ext.grid.GridPanel, {
var items = [this.repository.name];
var type = Sonia.repository.getTypeByName( this.repository.type );
+ var branches = false;
+ if (type && type.supportedCommands && type.supportedCommands.indexOf('BRANCHES') >= 0){
+
+ branches = true;
+
+ items.push('->','Branches:', ' ',{
+ id: 'branchComboBox',
+ xtype: 'repositoryBranchComboBox',
+ repositoryId: this.repository.id,
+ listeners: {
+ select: {
+ fn: this.selectBranch,
+ scope: this
+ }
+ }
+ });
+
+ }
+
if ( type && type.supportedCommands && type.supportedCommands.indexOf('TAGS') >= 0){
- var tagStore = new Sonia.rest.JsonStore({
- proxy: new Ext.data.HttpProxy({
- url: restUrl + 'repositories/' + this.repository.id + '/tags.json',
- method: 'GET',
- disableCaching: false
- }),
- root: 'tag',
- idProperty: 'name',
- fields: [ 'name', 'revision' ]
- });
+ if (branches){
+ items.push(' ');
+ } else {
+ items.push('->');
+ }
- items.push('->','Tags:', ' ',{
- xtype: 'combo',
- valueField: 'revision',
- displayField: 'name',
- typeAhead: false,
- editable: false,
- triggerAction: 'all',
- store: tagStore,
+ items.push('Tags:', ' ',{
+ id: 'tagComboBox',
+ xtype: 'repositoryTagComboBox',
+ repositoryId: this.repository.id,
listeners: {
select: {
fn: this.selectTag,
@@ -193,10 +203,18 @@ Sonia.repository.RepositoryBrowser = Ext.extend(Ext.grid.GridPanel, {
return tbar;
},
+ selectBranch: function(combo, rec){
+ this.selectRev(rec, Ext.getCmp('tagComboBox'));
+ },
+
selectTag: function(combo, rec){
+ this.selectRev(rec, Ext.getCmp('branchComboBox'));
+ },
+
+ selectRev: function(rec, comboToClear){
var tag = rec.get('name');
if (debug){
- console.debug('select tag ' + tag);
+ console.debug('select rev ' + tag);
}
this.revision = rec.get('revision');
this.getStore().load({
@@ -208,6 +226,11 @@ Sonia.repository.RepositoryBrowser = Ext.extend(Ext.grid.GridPanel, {
this.reRenderBottomBar(this.path);
this.updateHistory();
+
+ // clear other combobox
+ if (comboToClear){
+ comboToClear.clearValue();
+ }
},
loadStore: function(store, records, extra){
@@ -299,25 +322,7 @@ Sonia.repository.RepositoryBrowser = Ext.extend(Ext.grid.GridPanel, {
},
openFile: function(path){
- if ( debug ){
- console.debug( 'open file: ' + path );
- }
-
- var id = Sonia.repository.createContentId(
- this.repository,
- path,
- this.revision
- );
-
- main.addTab({
- id: id,
- path: path,
- revision: this.revision,
- repository: this.repository,
- xtype: 'contentPanel',
- closable: true,
- autoScroll: true
- });
+ Sonia.repository.openFile(this.repository, path, this.revision);
},
changeDirectory: function(path){
@@ -374,7 +379,12 @@ Sonia.repository.RepositoryBrowser = Ext.extend(Ext.grid.GridPanel, {
var bbar = this.getBottomToolbar();
bbar.removeAll();
- var parts = path.split('/');
+ var parts;
+ if (path){
+ parts = path.split('/');
+ } else {
+ parts = [];
+ }
var currentPath = '';
var items = [this.createFolderButton(currentPath, '')];
diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.settingsformpanel.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.settingsformpanel.js
index 5c6b902756..2b182a5de2 100644
--- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.settingsformpanel.js
+++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.settingsformpanel.js
@@ -78,9 +78,15 @@ Sonia.repository.SettingsFormPanel = Ext.extend(Sonia.repository.FormPanel, {
helpText: this.publicHelpText
}]
};
+
+ this.modifyDefaultConfig(config);
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.repository.SettingsFormPanel.superclass.initComponent.apply(this, arguments);
+ },
+
+ modifyDefaultConfig: function(config){
+
}
});
diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.tagcombobox.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.tagcombobox.js
new file mode 100644
index 0000000000..3f045a8cf6
--- /dev/null
+++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.tagcombobox.js
@@ -0,0 +1,65 @@
+/* *
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+Sonia.repository.TagComboBox = Ext.extend(Ext.form.ComboBox, {
+
+ repositoryId: null,
+
+ initComponent: function(){
+ var tagStore = new Sonia.rest.JsonStore({
+ proxy: new Ext.data.HttpProxy({
+ url: restUrl + 'repositories/' + this.repositoryId + '/tags.json',
+ method: 'GET',
+ disableCaching: false
+ }),
+ root: 'tag',
+ idProperty: 'name',
+ fields: [ 'name', 'revision' ]
+ });
+
+ var config = {
+ valueField: 'revision',
+ displayField: 'name',
+ typeAhead: false,
+ editable: false,
+ triggerAction: 'all',
+ store: tagStore
+ };
+
+ Ext.apply(this, Ext.apply(this.initialConfig, config));
+ Sonia.repository.TagComboBox.superclass.initComponent.apply(this, arguments);
+ }
+
+});
+
+// register xtype
+Ext.reg('repositoryTagComboBox', Sonia.repository.TagComboBox);
\ No newline at end of file
diff --git a/scm-webapp/src/main/webapp/resources/js/sonia.global.js b/scm-webapp/src/main/webapp/resources/js/sonia.global.js
index 32dec370a9..a43feb824b 100644
--- a/scm-webapp/src/main/webapp/resources/js/sonia.global.js
+++ b/scm-webapp/src/main/webapp/resources/js/sonia.global.js
@@ -37,6 +37,17 @@ Ext.Ajax.defaultHeaders = {
'X-SCM-Client': 'WUI'
};
+// XSRF protection
+Ext.Ajax.on('beforerequest', function(conn, options){
+ var token = Ext.util.Cookies.get('X-XSRF-Token');
+ if (token){
+ if (!options.headers){
+ options.headers = {};
+ }
+ options.headers['X-XSRF-Token'] = token;
+ }
+});
+
var state = null;
var admin = false;
diff --git a/scm-webapp/src/main/webapp/resources/js/sonia.scm.js b/scm-webapp/src/main/webapp/resources/js/sonia.scm.js
index 4e54c3af09..6d7e6a3257 100644
--- a/scm-webapp/src/main/webapp/resources/js/sonia.scm.js
+++ b/scm-webapp/src/main/webapp/resources/js/sonia.scm.js
@@ -78,6 +78,7 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
mainTabPanel: null,
infoPanels: [],
+ settingsForm: [],
scripts: [],
stylesheets: [],
@@ -96,6 +97,23 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
this.infoPanels[type] = panel;
},
+ registerSettingsForm: function(type, form){
+ this.settingsForm[type] = form;
+ },
+
+ getSettingsForm: function(type){
+ var rp = null;
+ var panel = this.settingsForm[type];
+ if ( ! panel ){
+ rp = {
+ xtype: 'repositorySettingsForm'
+ };
+ } else {
+ rp = Sonia.util.clone( panel );
+ }
+ return rp;
+ },
+
getInfoPanel: function(type){
var rp = null;
var panel = this.infoPanels[type];
diff --git a/scm-webapp/src/test/java/sonia/scm/filter/AdminSecurityFilterTest.java b/scm-webapp/src/test/java/sonia/scm/filter/AdminSecurityFilterTest.java
new file mode 100644
index 0000000000..b6a9c02a8d
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/filter/AdminSecurityFilterTest.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.filter;
+
+import com.github.sdorra.shiro.ShiroRule;
+import com.github.sdorra.shiro.SubjectAware;
+import org.apache.shiro.SecurityUtils;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.config.ScmConfiguration;
+
+/**
+ * Unit tests for {@link AdminSecurityFilter}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
+public class AdminSecurityFilterTest {
+
+ private AdminSecurityFilter securityFilter;
+
+ @Rule
+ public ShiroRule shiro = new ShiroRule();
+
+ /**
+ * Prepare object under test and mocks.
+ */
+ @Before
+ public void setUp(){
+ this.securityFilter = new AdminSecurityFilter(new ScmConfiguration());
+ }
+
+ /**
+ * Tests {@link AdminSecurityFilter#hasPermission(org.apache.shiro.subject.Subject)} as administrator.
+ */
+ @Test
+ @SubjectAware(username = "dent", password = "secret")
+ public void testHasPermissionAsAdministrator() {
+ assertTrue(securityFilter.hasPermission(SecurityUtils.getSubject()));
+ }
+
+ /**
+ * Tests {@link AdminSecurityFilter#hasPermission(org.apache.shiro.subject.Subject)} as user.
+ */
+ @Test
+ @SubjectAware(username = "trillian", password = "secret")
+ public void testHasPermissionAsUser() {
+ assertFalse(securityFilter.hasPermission(SecurityUtils.getSubject()));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java b/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java
new file mode 100644
index 0000000000..010c9bc1cf
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java
@@ -0,0 +1,130 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.filter;
+
+import com.github.sdorra.shiro.ShiroRule;
+import com.github.sdorra.shiro.SubjectAware;
+import java.io.IOException;
+import java.util.Map;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.slf4j.MDC;
+import sonia.scm.AbstractTestBase;
+
+/**
+ * Unit tests for {@link MDCFilter}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class MDCFilterTest extends AbstractTestBase {
+
+ @Rule
+ public ShiroRule shiro = new ShiroRule();
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ private final MDCFilter filter = new MDCFilter();
+
+ /**
+ * Tests {@link MDCFilter#doFilter(HttpServletRequest, HttpServletResponse, FilterChain)}.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ @SubjectAware(
+ username = "trillian",
+ password = "secret",
+ configuration = "classpath:sonia/scm/shiro-001.ini"
+ )
+ public void testDoFilter() throws IOException, ServletException
+ {
+ when(request.getRequestURI()).thenReturn("api/v1/repositories");
+ when(request.getRemoteAddr()).thenReturn("127.0.0.1");
+ when(request.getRemoteHost()).thenReturn("localhost");
+ when(request.getMethod()).thenReturn("GET");
+
+ MDCCapturingFilterChain chain = new MDCCapturingFilterChain();
+ filter.doFilter(request, response, chain);
+
+ assertNotNull(chain.ctx);
+ assertEquals("trillian", chain.ctx.get(MDCFilter.MDC_USERNAME));
+ assertEquals("api/v1/repositories", chain.ctx.get(MDCFilter.MDC_REQUEST_URI));
+ assertEquals("127.0.0.1", chain.ctx.get(MDCFilter.MDC_CLIEN_IP));
+ assertEquals("localhost", chain.ctx.get(MDCFilter.MDC_CLIEN_HOST));
+ assertEquals("GET", chain.ctx.get(MDCFilter.MDC_REQUEST_METHOD));
+ }
+
+ /**
+ * Tests {@link MDCFilter#doFilter(HttpServletRequest, HttpServletResponse, FilterChain)} as anonymous user.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ @SubjectAware
+ public void testDoFilterAsAnonymous() throws IOException, ServletException {
+ MDCCapturingFilterChain chain = new MDCCapturingFilterChain();
+ filter.doFilter(request, response, chain);
+
+ assertNotNull(chain.ctx);
+ assertEquals("anonymous", chain.ctx.get(MDCFilter.MDC_USERNAME));
+ }
+
+ private static class MDCCapturingFilterChain implements FilterChain {
+
+ private Map ctx;
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
+ this.ctx = MDC.getCopyOfContextMap();
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/filter/SecurityFilterTest.java b/scm-webapp/src/test/java/sonia/scm/filter/SecurityFilterTest.java
new file mode 100644
index 0000000000..13333cc223
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/filter/SecurityFilterTest.java
@@ -0,0 +1,238 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.filter;
+
+import com.github.sdorra.shiro.ShiroRule;
+import com.github.sdorra.shiro.SubjectAware;
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.SCMContext;
+import sonia.scm.config.ScmConfiguration;
+import sonia.scm.user.User;
+import sonia.scm.user.UserTestData;
+
+/**
+ * Unit tests for {@link SecurityFilter}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
+public class SecurityFilterTest {
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Captor
+ private ArgumentCaptor requestCaptor;
+
+ @Mock
+ private HttpServletResponse response;
+
+ @Captor
+ private ArgumentCaptor responseCaptor;
+
+ @Mock
+ private FilterChain chain;
+
+ private ScmConfiguration configuration;
+
+ private SecurityFilter securityFilter;
+
+ @Rule
+ public ShiroRule shiro = new ShiroRule();
+
+ /**
+ * Prepare object under test and mocks.
+ */
+ @Before
+ public void setUp(){
+ this.configuration = new ScmConfiguration();
+ this.securityFilter = new SecurityFilter(configuration);
+
+ when(request.getContextPath()).thenReturn("/scm");
+ }
+
+ /**
+ * Tests filter on authentication endpoint.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoOnAuthenticationUrl() throws IOException, ServletException {
+ when(request.getRequestURI()).thenReturn("/scm/api/rest/authentication");
+ securityFilter.doFilter(request, response, chain);
+ verify(request, never()).setAttribute(Mockito.anyString(), Mockito.any());
+ verify(chain).doFilter(request, response);
+ }
+
+ /**
+ * Tests filter without prior authentication.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testAnonymous() throws IOException, ServletException {
+ when(request.getRequestURI()).thenReturn("/scm/api");
+ securityFilter.doFilter(request, response, chain);
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+
+ /**
+ * Tests filter without prior authentication and enabled anonymous access.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testAnonymousWithAccessEnabled() throws IOException, ServletException {
+ when(request.getRequestURI()).thenReturn("/scm/api");
+ configuration.setAnonymousAccessEnabled(true);
+
+ // execute
+ securityFilter.doFilter(request, response, chain);
+
+ // verify and capture
+ verify(request).setAttribute(SecurityFilter.ATTRIBUTE_REMOTE_USER, SCMContext.USER_ANONYMOUS);
+ verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture());
+
+ // assert
+ HttpServletRequest captured = requestCaptor.getValue();
+ assertEquals(SCMContext.USER_ANONYMOUS, captured.getRemoteUser());
+ }
+
+ /**
+ * Tests filter with prior authentication.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testAuthenticated() throws IOException, ServletException {
+ authenticateUser(UserTestData.createTrillian());
+ when(request.getRequestURI()).thenReturn("/scm/api");
+
+ // execute
+ securityFilter.doFilter(request, response, chain);
+
+ // verify and capture
+ verify(request).setAttribute(SecurityFilter.ATTRIBUTE_REMOTE_USER, "trillian");
+ verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture());
+
+ // assert
+ HttpServletRequest captured = requestCaptor.getValue();
+ assertEquals("trillian", captured.getRemoteUser());
+ }
+
+ /**
+ * Tests filter without permissions.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testForbidden() throws IOException, ServletException {
+ authenticateUser(UserTestData.createTrillian());
+ when(request.getRequestURI()).thenReturn("/scm/api");
+
+ // execute
+ securityFilter = new AccessForbiddenSecurityFilter(configuration);
+ securityFilter.doFilter(request, response, chain);
+
+ // assert
+ verify(response).sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+
+ /**
+ * Tests filter unauthenticated and without permissions.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testUnauthorized() throws IOException, ServletException {
+ when(request.getRequestURI()).thenReturn("/scm/api");
+
+ // execute
+ securityFilter = new AccessForbiddenSecurityFilter(configuration);
+ securityFilter.doFilter(request, response, chain);
+
+ // assert
+ verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+
+ private void authenticateUser(User user) {
+ SimplePrincipalCollection spc = new SimplePrincipalCollection();
+ spc.add(user.getName(), "unit-test");
+ spc.add(user, "unit-test");
+
+ Subject subject = new Subject.Builder()
+ .authenticated(true)
+ .principals(spc)
+ .buildSubject();
+
+ shiro.setSubject(subject);
+ }
+
+ private static class AccessForbiddenSecurityFilter extends SecurityFilter {
+
+ private AccessForbiddenSecurityFilter(ScmConfiguration configuration) {
+ super(configuration);
+ }
+
+ @Override
+ protected boolean hasPermission(Subject subject) {
+ return false;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/it/AnonymousAccessITCase.java b/scm-webapp/src/test/java/sonia/scm/it/AnonymousAccessITCase.java
index 8122a9b26d..cf27804d2e 100644
--- a/scm-webapp/src/test/java/sonia/scm/it/AnonymousAccessITCase.java
+++ b/scm-webapp/src/test/java/sonia/scm/it/AnonymousAccessITCase.java
@@ -286,7 +286,7 @@ public class AnonymousAccessITCase
File directory = temporaryFolder.newFolder();
RepositoryClient client = null;
- String url = repository.createUrl(URL);
+ String url = repository.createUrl(BASE_URL);
if ((username != null) && (password != null))
{
diff --git a/scm-webapp/src/test/java/sonia/scm/it/ChangesetViewerITCase.java b/scm-webapp/src/test/java/sonia/scm/it/ChangesetViewerITCase.java
index 99f775eaa0..adb055d46a 100644
--- a/scm-webapp/src/test/java/sonia/scm/it/ChangesetViewerITCase.java
+++ b/scm-webapp/src/test/java/sonia/scm/it/ChangesetViewerITCase.java
@@ -37,7 +37,6 @@ package sonia.scm.it;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -264,7 +263,7 @@ public class ChangesetViewerITCase extends AbstractAdminITCaseBase
throws RepositoryClientException
{
return RepositoryClientFactory.createClient(repositoryType, localDirectory,
- repository.createUrl(URL), IntegrationTestUtil.ADMIN_USERNAME,
+ repository.createUrl(BASE_URL), IntegrationTestUtil.ADMIN_USERNAME,
IntegrationTestUtil.ADMIN_PASSWORD);
}
diff --git a/scm-webapp/src/test/java/sonia/scm/it/IntegrationTestUtil.java b/scm-webapp/src/test/java/sonia/scm/it/IntegrationTestUtil.java
index 7d5fa754cd..078b12acc2 100644
--- a/scm-webapp/src/test/java/sonia/scm/it/IntegrationTestUtil.java
+++ b/scm-webapp/src/test/java/sonia/scm/it/IntegrationTestUtil.java
@@ -77,11 +77,12 @@ public final class IntegrationTestUtil
/** Field description */
public static final String ADMIN_USERNAME = "scmadmin";
-
- public static final String URL = "http://localhost:8081/scm";
- /** Field description */
- public static final String BASE_URL = "http://localhost:8081/scm/api/rest/";
+ /** scm-manager base url */
+ public static final String BASE_URL = "http://localhost:8081/scm/";
+
+ /** scm-manager base url for the rest api */
+ public static final String REST_BASE_URL = BASE_URL.concat("api/rest/");
/** Field description */
public static final String EXTENSION = ".xml";
@@ -256,7 +257,7 @@ public final class IntegrationTestUtil
*/
public static String createResourceUrl(String url)
{
- return BASE_URL.concat(url).concat(EXTENSION);
+ return REST_BASE_URL.concat(url).concat(EXTENSION);
}
/**
diff --git a/scm-webapp/src/test/java/sonia/scm/it/RepositoryHookITCase.java b/scm-webapp/src/test/java/sonia/scm/it/RepositoryHookITCase.java
new file mode 100644
index 0000000000..ad8fddb994
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/it/RepositoryHookITCase.java
@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.it;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import com.sun.jersey.api.client.WebResource;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import org.junit.After;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import sonia.scm.debug.DebugHookData;
+import static sonia.scm.it.IntegrationTestUtil.createResource;
+import static sonia.scm.it.RepositoryITUtil.createRepository;
+import static sonia.scm.it.RepositoryITUtil.deleteRepository;
+import sonia.scm.repository.Changeset;
+import sonia.scm.repository.Person;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryTestData;
+import sonia.scm.repository.client.api.RepositoryClient;
+import sonia.scm.repository.client.api.RepositoryClientFactory;
+import sonia.scm.util.IOUtil;
+
+/**
+ * Integration tests for repository hooks.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(Parameterized.class)
+public class RepositoryHookITCase extends AbstractAdminITCaseBase
+{
+
+ private static final long WAIT_TIME = 125;
+
+ private static final RepositoryClientFactory REPOSITORY_CLIENT_FACTORY = new RepositoryClientFactory();
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ private final String repositoryType;
+ private Repository repository;
+ private File workingCopy;
+ private RepositoryClient repositoryClient;
+
+ /**
+ * Constructs a new instance with a repository type.
+ *
+ * @param repositoryType repository type
+ */
+ public RepositoryHookITCase(String repositoryType)
+ {
+ this.repositoryType = repositoryType;
+ }
+
+ /**
+ * Creates a test repository.
+ *
+ * @throws IOException
+ */
+ @Before
+ public void setUpTestRepository() throws IOException
+ {
+ repository = RepositoryTestData.createHeartOfGold(repositoryType);
+ repository = createRepository(client, repository);
+ workingCopy = tempFolder.newFolder();
+ repositoryClient = createRepositoryClient();
+ }
+
+ /**
+ * Removes the tests repository.
+ */
+ @After
+ public void removeTestRepository()
+ {
+ deleteRepository(client, repository.getId());
+ }
+
+ /**
+ * Tests that the debug service has received the commit.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ @Test
+ public void testSimpleHook() throws IOException, InterruptedException
+ {
+ // push commit
+ Files.write("a", new File(workingCopy, "a.txt"), Charsets.UTF_8);
+ repositoryClient.getAddCommand().add("a.txt");
+ Changeset changeset = repositoryClient.getCommitCommand().commit(
+ new Person("scmadmin", "scmadmin@scm-manager.org"), "added a"
+ );
+ repositoryClient.getPushCommand().push();
+
+ // wait some time, because the debug hook is asnychron
+ Thread.sleep(WAIT_TIME);
+
+ // check debug servlet for pushed commit
+ WebResource wr = createResource(client, "debug/" + repository.getId() + "/post-receive/last");
+ DebugHookData data = wr.get(DebugHookData.class);
+ assertNotNull(data);
+ assertThat(data.getChangesets(), contains(changeset.getId()));
+ }
+
+ /**
+ * Tests that the debug service receives only new commits.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ @Test
+ public void testOnlyNewCommit() throws IOException, InterruptedException
+ {
+ // push commit
+ Files.write("a", new File(workingCopy, "a.txt"), Charsets.UTF_8);
+ repositoryClient.getAddCommand().add("a.txt");
+ Changeset a = repositoryClient.getCommitCommand().commit(
+ new Person("scmadmin", "scmadmin@scm-manager.org"), "added a"
+ );
+ repositoryClient.getPushCommand().push();
+
+ // create branch, commit and push again
+ repositoryClient.getBranchCommand().branch("feature/added-b");
+ Files.write("b", new File(workingCopy, "b.txt"), Charsets.UTF_8);
+ repositoryClient.getAddCommand().add("a.txt");
+ Changeset b = repositoryClient.getCommitCommand().commit(
+ new Person("scmadmin", "scmadmin@scm-manager.org"), "added b"
+ );
+ repositoryClient.getPushCommand().push();
+
+ // wait some time, because the debug hook is asnychron
+ Thread.sleep(WAIT_TIME);
+
+ // check debug servlet that only one commit is present
+ WebResource wr = createResource(client, "debug/" + repository.getId() + "/post-receive/last");
+ DebugHookData data = wr.get(DebugHookData.class);
+ assertNotNull(data);
+ assertThat(data.getChangesets(), allOf(
+ contains(b.getId()),
+ not(
+ contains(a.getId())
+ )
+ ));
+ }
+
+ private RepositoryClient createRepositoryClient() throws IOException
+ {
+ return REPOSITORY_CLIENT_FACTORY.create(repositoryType,
+ IntegrationTestUtil.BASE_URL + repositoryType + "/" + repository.getName(),
+ IntegrationTestUtil.ADMIN_USERNAME, IntegrationTestUtil.ADMIN_PASSWORD, workingCopy
+ );
+ }
+
+
+ /**
+ * Returns repository types a test parameter.
+ *
+ * @return repository types test parameter
+ */
+ @Parameters
+ public static Collection createParameters()
+ {
+ Collection params = Lists.newArrayList();
+ params.add(new String[] { "git" });
+ // params.add(new String[] { "svn" });
+ if (IOUtil.search("hg") != null)
+ {
+ params.add(new String[] { "hg" });
+ }
+ return params;
+ }
+
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/it/RepositoryITCase.java b/scm-webapp/src/test/java/sonia/scm/it/RepositoryITCase.java
index b3d35a6c17..9ee0cadf58 100644
--- a/scm-webapp/src/test/java/sonia/scm/it/RepositoryITCase.java
+++ b/scm-webapp/src/test/java/sonia/scm/it/RepositoryITCase.java
@@ -35,7 +35,8 @@ package sonia.scm.it;
//~--- non-JDK imports --------------------------------------------------------
-import org.junit.After;
+import com.google.common.collect.Lists;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -45,23 +46,23 @@ import sonia.scm.repository.Permission;
import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryTestData;
-import sonia.scm.util.IOUtil;
import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
import static sonia.scm.it.IntegrationTestUtil.*;
import static sonia.scm.it.RepositoryITUtil.*;
//~--- JDK imports ------------------------------------------------------------
-import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.WebResource;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import org.junit.After;
+import sonia.scm.util.IOUtil;
/**
*
@@ -90,34 +91,9 @@ public class RepositoryITCase extends AbstractAdminITCaseBase
*
* @return
*/
- @Parameters
- public static Collection createParameters()
- {
- Collection params = new ArrayList();
-
- params.add(new String[] { "git" });
-
- params.add(new String[] { "svn" });
-
- if (IOUtil.search("hg") != null)
- {
- params.add(new String[] { "hg" });
- }
-
- return params;
- }
-
- /**
- * Method description
- *
- */
@After
public void cleanup()
{
- Client client = createClient();
-
- authenticateAdmin(client);
-
Collection repositories =
createResource(client,
"repositories").get(new GenericType>() {}
@@ -137,6 +113,29 @@ public class RepositoryITCase extends AbstractAdminITCaseBase
/**
* Method description
*
+ *
+ * @return
+ */
+ @Parameters
+ public static Collection createParameters()
+ {
+ Collection params = Lists.newArrayList();
+
+ params.add(new String[] { "git" });
+ params.add(new String[] { "svn" });
+
+ if (IOUtil.search("hg") != null)
+ {
+ params.add(new String[] { "hg" });
+ }
+
+ return params;
+ }
+
+ /**
+ * Method description
+ *
+>>>>>>> merge rev
*/
@Test
public void create()
@@ -165,21 +164,18 @@ public class RepositoryITCase extends AbstractAdminITCaseBase
* Method description
*
*/
-
@Test
public void doubleCreate()
{
Repository repository = RepositoryTestData.create42Puzzle(repositoryType);
repository = createRepository(client, repository);
-
+
WebResource wr = createResource(client, "repositories");
ClientResponse response = wr.post(ClientResponse.class, repository);
-
+
assertNotNull(response);
-
- // TODO should be 409?
- assertEquals(500, response.getStatus());
+ assertThat(response.getStatus(), not(lessThanOrEqualTo(400)));
}
/**
diff --git a/scm-webapp/src/test/java/sonia/scm/it/RepositoryITCaseBase.java b/scm-webapp/src/test/java/sonia/scm/it/RepositoryITCaseBase.java
index edfeffe3d3..5df0b56f9b 100644
--- a/scm-webapp/src/test/java/sonia/scm/it/RepositoryITCaseBase.java
+++ b/scm-webapp/src/test/java/sonia/scm/it/RepositoryITCaseBase.java
@@ -142,7 +142,7 @@ public class RepositoryITCaseBase
{
RepositoryClient rc =
RepositoryClientFactory.createClient(repository.getType(), directory,
- repository.createUrl(URL), username, password);
+ repository.createUrl(BASE_URL), username, password);
rc.init();
addTestFiles(rc);
@@ -343,7 +343,7 @@ public class RepositoryITCaseBase
throws RepositoryClientException
{
return RepositoryClientFactory.createClient(repository.getType(),
- directory, repository.createUrl(URL), user.getName(), password);
+ directory, repository.createUrl(BASE_URL), user.getName(), password);
}
//~--- fields ---------------------------------------------------------------
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java
new file mode 100644
index 0000000000..faac9a99e5
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java
@@ -0,0 +1,376 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import sonia.scm.config.ScmConfiguration;
+import sonia.scm.net.TrustAllHostnameVerifier;
+import sonia.scm.util.HttpUtil;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.*;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import java.net.HttpURLConnection;
+import java.net.SocketAddress;
+import java.net.URL;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+import sonia.scm.net.SSLContextProvider;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultAdvancedHttpClientTest
+{
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testApplyBaseSettings() throws IOException
+ {
+ new AdvancedHttpRequest(client, HttpMethod.GET,
+ "https://www.scm-manager.org").request();
+ verify(connection).setRequestMethod(HttpMethod.GET);
+ verify(connection).setReadTimeout(DefaultAdvancedHttpClient.TIMEOUT_RAED);
+ verify(connection).setConnectTimeout(
+ DefaultAdvancedHttpClient.TIMEOUT_CONNECTION);
+ verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0");
+ }
+
+ @Test(expected = ContentTransformerNotFoundException.class)
+ public void testContentTransformerNotFound(){
+ client.createTransformer(String.class, "text/plain");
+ }
+
+ @Test
+ public void testContentTransformer(){
+ ContentTransformer transformer = mock(ContentTransformer.class);
+ when(transformer.isResponsible(String.class, "text/plain")).thenReturn(Boolean.TRUE);
+ transformers.add(transformer);
+ ContentTransformer t = client.createTransformer(String.class, "text/plain");
+ assertSame(transformer, t);
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testApplyContent() throws IOException
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ when(connection.getOutputStream()).thenReturn(baos);
+
+ AdvancedHttpRequestWithBody request =
+ new AdvancedHttpRequestWithBody(client, HttpMethod.PUT,
+ "https://www.scm-manager.org");
+
+ request.stringContent("test").request();
+ verify(connection).setDoOutput(true);
+ verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "4");
+ assertEquals("test", baos.toString("UTF-8"));
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testApplyHeaders() throws IOException
+ {
+ AdvancedHttpRequest request = new AdvancedHttpRequest(client,
+ HttpMethod.POST,
+ "http://www.scm-manager.org");
+
+ request.header("Header-One", "One").header("Header-Two", "Two").request();
+ verify(connection).setRequestMethod(HttpMethod.POST);
+ verify(connection).addRequestProperty("Header-One", "One");
+ verify(connection).addRequestProperty("Header-Two", "Two");
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testApplyMultipleHeaders() throws IOException
+ {
+ AdvancedHttpRequest request = new AdvancedHttpRequest(client,
+ HttpMethod.POST,
+ "http://www.scm-manager.org");
+
+ request.header("Header-One", "One").header("Header-One", "Two").request();
+ verify(connection).setRequestMethod(HttpMethod.POST);
+ verify(connection).addRequestProperty("Header-One", "One");
+ verify(connection).addRequestProperty("Header-One", "Two");
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testBodyRequestWithoutContent() throws IOException
+ {
+ AdvancedHttpRequestWithBody request =
+ new AdvancedHttpRequestWithBody(client, HttpMethod.PUT,
+ "https://www.scm-manager.org");
+
+ request.request();
+ verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0");
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testDisableCertificateValidation() throws IOException
+ {
+ AdvancedHttpRequest request = new AdvancedHttpRequest(client,
+ HttpMethod.GET,
+ "https://www.scm-manager.org");
+
+ request.disableCertificateValidation(true).request();
+ verify(connection).setSSLSocketFactory(any(SSLSocketFactory.class));
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testDisableHostnameValidation() throws IOException
+ {
+ AdvancedHttpRequest request = new AdvancedHttpRequest(client,
+ HttpMethod.GET,
+ "https://www.scm-manager.org");
+
+ request.disableHostnameValidation(true).request();
+ verify(connection).setHostnameVerifier(any(TrustAllHostnameVerifier.class));
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testIgnoreProxy() throws IOException
+ {
+ configuration.setProxyServer("proxy.scm-manager.org");
+ configuration.setProxyPort(8090);
+ configuration.setEnableProxy(true);
+ new AdvancedHttpRequest(client, HttpMethod.GET,
+ "https://www.scm-manager.org").ignoreProxySettings(true).request();
+ assertFalse(client.proxyConnection);
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testProxyConnection() throws IOException
+ {
+ configuration.setProxyServer("proxy.scm-manager.org");
+ configuration.setProxyPort(8090);
+ configuration.setEnableProxy(true);
+ new AdvancedHttpRequest(client, HttpMethod.GET,
+ "https://www.scm-manager.org").request();
+ assertTrue(client.proxyConnection);
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testProxyWithAuthentication() throws IOException
+ {
+ configuration.setProxyServer("proxy.scm-manager.org");
+ configuration.setProxyPort(8090);
+ configuration.setProxyUser("tricia");
+ configuration.setProxyPassword("tricias secret");
+ configuration.setEnableProxy(true);
+ new AdvancedHttpRequest(client, HttpMethod.GET,
+ "https://www.scm-manager.org").request();
+ assertTrue(client.proxyConnection);
+ verify(connection).addRequestProperty(
+ DefaultAdvancedHttpClient.HEADER_PROXY_AUTHORIZATION,
+ "Basic dHJpY2lhOnRyaWNpYXMgc2VjcmV0");
+ }
+
+ //~--- set methods ----------------------------------------------------------
+
+ /**
+ * Method description
+ *
+ */
+ @Before
+ public void setUp()
+ {
+ configuration = new ScmConfiguration();
+ transformers = new HashSet();
+ client = new TestingAdvacedHttpClient(configuration, transformers);
+ }
+
+ //~--- inner classes --------------------------------------------------------
+
+ /**
+ * Class description
+ *
+ *
+ * @version Enter version here..., 15/05/01
+ * @author Enter your name here...
+ */
+ public class TestingAdvacedHttpClient extends DefaultAdvancedHttpClient
+ {
+
+ /**
+ * Constructs ...
+ *
+ *
+ * @param configuration
+ * @param transformers
+ */
+ public TestingAdvacedHttpClient(ScmConfiguration configuration,
+ Set transformers)
+ {
+ super(configuration, transformers, new SSLContextProvider());
+ }
+
+ //~--- methods ------------------------------------------------------------
+
+ /**
+ * Method description
+ *
+ *
+ * @param url
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+ @Override
+ protected HttpURLConnection createConnection(URL url) throws IOException
+ {
+ return connection;
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @param url
+ * @param address
+ *
+ * @return
+ *
+ * @throws IOException
+ */
+ @Override
+ protected HttpURLConnection createProxyConnecton(URL url,
+ SocketAddress address)
+ throws IOException
+ {
+ proxyConnection = true;
+
+ return connection;
+ }
+
+ //~--- fields -------------------------------------------------------------
+
+ /** Field description */
+ private boolean proxyConnection = false;
+ }
+
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** Field description */
+ private TestingAdvacedHttpClient client;
+
+ /** Field description */
+ private ScmConfiguration configuration;
+
+ /** Field description */
+ @Mock
+ private HttpsURLConnection connection;
+
+ /** Field description */
+ private Set transformers;
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java
new file mode 100644
index 0000000000..0a2b4cbdb3
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.io.ByteSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import sonia.scm.config.ScmConfiguration;
+
+import static org.hamcrest.Matchers.*;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.*;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import java.net.HttpURLConnection;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import sonia.scm.net.SSLContextProvider;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultAdvancedHttpResponseTest
+{
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testContentAsByteSource() throws IOException
+ {
+ ByteArrayInputStream bais =
+ new ByteArrayInputStream("test".getBytes(Charsets.UTF_8));
+
+ when(connection.getInputStream()).thenReturn(bais);
+
+ AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client,
+ connection, 200, "OK");
+ ByteSource content = response.contentAsByteSource();
+
+ assertEquals("test", content.asCharSource(Charsets.UTF_8).read());
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testContentAsByteSourceWithFailedRequest() throws IOException
+ {
+ ByteArrayInputStream bais =
+ new ByteArrayInputStream("test".getBytes(Charsets.UTF_8));
+
+ when(connection.getInputStream()).thenThrow(IOException.class);
+ when(connection.getErrorStream()).thenReturn(bais);
+
+ AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client,
+ connection, 404, "NOT FOUND");
+ ByteSource content = response.contentAsByteSource();
+
+ assertEquals("test", content.asCharSource(Charsets.UTF_8).read());
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testGetHeaders()
+ {
+ LinkedHashMap> map = Maps.newLinkedHashMap();
+ List test = Lists.newArrayList("One", "Two");
+
+ map.put("Test", test);
+ when(connection.getHeaderFields()).thenReturn(map);
+
+ AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client,
+ connection, 200, "OK");
+ Multimap headers = response.getHeaders();
+
+ assertThat(headers.get("Test"), contains("One", "Two"));
+ assertTrue(headers.get("Test-2").isEmpty());
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** Field description */
+ private final DefaultAdvancedHttpClient client =
+ new DefaultAdvancedHttpClient(new ScmConfiguration(),
+ new HashSet(), new SSLContextProvider());
+
+ /** Field description */
+ @Mock
+ private HttpURLConnection connection;
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java
new file mode 100644
index 0000000000..8094d1c06f
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java
@@ -0,0 +1,167 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteSource;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.*;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.IOException;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+public class JsonContentTransformerTest
+{
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testDoNotFailOnUnknownProperties() {
+ ByteSource bs = ByteSource.wrap("{\"value\": \"test\", \"other\": \"test2\"}".getBytes(Charsets.UTF_8));
+ TestObject obj = transformer.unmarshall(TestObject.class, bs);
+ assertEquals("test", obj.value);
+ }
+
+ /**
+ * Method description
+ *
+ */
+ @Test
+ public void testIsResponsible()
+ {
+ assertTrue(transformer.isResponsible(String.class, "application/json"));
+ assertTrue(transformer.isResponsible(String.class, "application/json;charset=UTF-8"));
+ assertFalse(transformer.isResponsible(String.class, "text/plain"));
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testMarshallAndUnmarshall() throws IOException
+ {
+ ByteSource bs = transformer.marshall(new TestObject("test"));
+ TestObject to = transformer.unmarshall(TestObject.class, bs);
+
+ assertEquals("test", to.value);
+ }
+
+ /**
+ * Method description
+ *
+ *
+ * @throws IOException
+ */
+ @Test(expected = ContentTransformerException.class)
+ public void testUnmarshallIOException() throws IOException
+ {
+ ByteSource bs = mock(ByteSource.class);
+
+ when(bs.openBufferedStream()).thenThrow(IOException.class);
+ transformer.unmarshall(String.class, bs);
+ }
+
+ //~--- inner classes --------------------------------------------------------
+
+ /**
+ * Class description
+ *
+ *
+ * @version Enter version here..., 15/10/21
+ * @author Enter your name here...
+ */
+ @XmlRootElement(name = "test")
+ @XmlAccessorType(XmlAccessType.FIELD)
+ private static class TestObject
+ {
+
+ /**
+ * Constructs ...
+ *
+ */
+ public TestObject() {}
+
+ /**
+ * Constructs ...
+ *
+ *
+ * @param value
+ */
+ public TestObject(String value)
+ {
+ this.value = value;
+ }
+
+ //~--- fields -------------------------------------------------------------
+
+ /** Field description */
+ private String value;
+ }
+
+
+ /**
+ * Class description
+ *
+ *
+ * @version Enter version here..., 15/10/21
+ * @author Enter your name here...
+ */
+ private static class TestObject2 {}
+
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** Field description */
+ private final JsonContentTransformer transformer = new JsonContentTransformer();
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/XmlContentTransformerTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/XmlContentTransformerTest.java
new file mode 100644
index 0000000000..ce87752b4f
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/XmlContentTransformerTest.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.io.ByteSource;
+import java.io.IOException;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+public class XmlContentTransformerTest {
+
+ private final XmlContentTransformer transformer = new XmlContentTransformer();
+
+ @Test
+ public void testIsResponsible()
+ {
+ assertTrue(transformer.isResponsible(String.class, "application/xml"));
+ assertTrue(transformer.isResponsible(String.class, "application/xml;charset=UTF-8"));
+ assertFalse(transformer.isResponsible(String.class, "text/plain"));
+ }
+
+ @Test
+ public void testMarshallAndUnmarshall() throws IOException{
+ ByteSource bs = transformer.marshall(new TestObject("test"));
+ TestObject to = transformer.unmarshall(TestObject.class, bs);
+ assertEquals("test", to.value);
+ }
+
+ @Test(expected = ContentTransformerException.class)
+ public void testUnmarshallIOException() throws IOException{
+ ByteSource bs = mock(ByteSource.class);
+ when(bs.openBufferedStream()).thenThrow(IOException.class);
+ transformer.unmarshall(String.class, bs);
+ }
+
+ private static class TestObject2 {}
+
+ @XmlRootElement(name = "test")
+ @XmlAccessorType(XmlAccessType.FIELD)
+ private static class TestObject {
+
+ private String value;
+
+ public TestObject()
+ {
+ }
+
+ public TestObject(String value)
+ {
+ this.value = value;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/resources/AbstractResourceManagerTest.java b/scm-webapp/src/test/java/sonia/scm/resources/AbstractResourceManagerTest.java
new file mode 100644
index 0000000000..184831f15a
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/resources/AbstractResourceManagerTest.java
@@ -0,0 +1,95 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.resources;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.hamcrest.Matchers.*;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.plugin.PluginLoader;
+
+/**
+ * Unit tests for {@link AbstractResourceManager}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AbstractResourceManagerTest extends ResourceManagerTestBase
+{
+
+ private DummyResourceManager resourceManager;
+
+ @Before
+ public void setUp()
+ {
+ Set resourceHandlers = ImmutableSet.of(resourceHandler);
+ resourceManager = new DummyResourceManager(pluginLoader, resourceHandlers);
+ }
+
+ /**
+ * Test {@link AbstractResourceManager#getScriptResources()} in the correct order.
+ *
+ * @throws java.io.IOException
+ *
+ * @see Issue 809
+ */
+ @Test
+ public void testGetScriptResources() throws IOException
+ {
+ appendScriptResources("a/b.js", "z/a.js", "a/a.js");
+ List scripts = resourceManager.getScriptResources();
+ assertThat(scripts, contains("a/a.js", "a/b.js", "z/a.js"));
+ }
+
+ private static class DummyResourceManager extends AbstractResourceManager
+ {
+
+ public DummyResourceManager(PluginLoader pluginLoader, Set resourceHandlers)
+ {
+ super(pluginLoader, resourceHandlers);
+ }
+
+ @Override
+ protected void collectResources(Map resourceMap)
+ {
+ }
+
+ }
+
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/resources/DefaultResourceManagerTest.java b/scm-webapp/src/test/java/sonia/scm/resources/DefaultResourceManagerTest.java
new file mode 100644
index 0000000000..a082656016
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/resources/DefaultResourceManagerTest.java
@@ -0,0 +1,76 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.resources;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ * Unit tests for {@link DefaultResourceManager}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultResourceManagerTest extends ResourceManagerTestBase {
+
+ private DefaultResourceManager resourceManager;
+
+ /**
+ * Set up {@link DefaultResourceManager} for tests.
+ */
+ @Before
+ public void setUp()
+ {
+ Set resourceHandlers = ImmutableSet.of(resourceHandler);
+ resourceManager = new DefaultResourceManager(pluginLoader, resourceHandlers);
+ }
+
+ /**
+ * Test {@link DefaultResourceManager#getResources(sonia.scm.resources.ResourceType)} method.
+ * @throws java.io.IOException
+ */
+ @Test
+ public void testGetResources() throws IOException
+ {
+ appendScriptResources("a/b.js", "z/a.js", "a/a.js");
+ List resources = resourceManager.getResources(ResourceType.SCRIPT);
+ assertEquals(1, resources.size());
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/resources/ResourceManagerTestBase.java b/scm-webapp/src/test/java/sonia/scm/resources/ResourceManagerTestBase.java
new file mode 100644
index 0000000000..164caf6f8c
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/resources/ResourceManagerTestBase.java
@@ -0,0 +1,112 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.resources;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+import javax.servlet.ServletContext;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mock;
+import static org.mockito.Mockito.when;
+import sonia.scm.plugin.Plugin;
+import sonia.scm.plugin.PluginCondition;
+import sonia.scm.plugin.PluginInformation;
+import sonia.scm.plugin.PluginLoader;
+import sonia.scm.plugin.PluginResources;
+import sonia.scm.plugin.PluginWrapper;
+import sonia.scm.plugin.WebResourceLoader;
+
+/**
+ * Base class for {@link ResourceManager} tests.
+ *
+ * @author Sebastian Sdorra
+ */
+public abstract class ResourceManagerTestBase
+{
+
+ @Mock
+ protected ServletContext servletContext;
+
+ @Mock
+ protected PluginLoader pluginLoader;
+
+ @Mock
+ protected ResourceHandler resourceHandler;
+
+ @Mock
+ protected WebResourceLoader webResourceLoader;
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ /**
+ * Append scripts resources to plugin loader.
+ *
+ * @param resources resource names
+ *
+ * @throws IOException
+ */
+ protected void appendScriptResources(String... resources) throws IOException
+ {
+ Set scripts = Sets.newHashSet(resources);
+ Set styles = Sets.newHashSet();
+ Set dependencies = Sets.newHashSet();
+
+
+ Plugin plugin = new Plugin(
+ 2,
+ new PluginInformation(),
+ new PluginResources(scripts, styles),
+ new PluginCondition(),
+ false,
+ dependencies
+ );
+
+ Path pluginPath = tempFolder.newFolder().toPath();
+
+ PluginWrapper wrapper = new PluginWrapper(
+ plugin,
+ Thread.currentThread().getContextClassLoader(),
+ webResourceLoader,
+ pluginPath
+ );
+
+ List plugins = ImmutableList.of(wrapper);
+
+ when(pluginLoader.getInstalledPlugins()).thenReturn(plugins);
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/schedule/InjectionEnabledJobTest.java b/scm-webapp/src/test/java/sonia/scm/schedule/InjectionEnabledJobTest.java
new file mode 100644
index 0000000000..9545577c9f
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/schedule/InjectionEnabledJobTest.java
@@ -0,0 +1,168 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.schedule;
+
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import org.junit.Test;
+import static org.mockito.Mockito.*;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import sonia.scm.web.security.AdministrationContext;
+import sonia.scm.web.security.PrivilegedAction;
+
+/**
+ * Unit tests for {@link InjectionEnabledJob}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class InjectionEnabledJobTest {
+
+ @Mock
+ private Injector injector;
+
+ @Mock
+ private JobDataMap dataMap;
+
+ @Mock
+ private JobDetail detail;
+
+ @Mock
+ private JobExecutionContext jec;
+
+ @Mock
+ private Provider runnable;
+
+ @Mock
+ private AdministrationContext context;
+
+ @Rule
+ public ExpectedException expected = ExpectedException.none();
+
+ /**
+ * Tests {@link InjectionEnabledJob#execute(org.quartz.JobExecutionContext)} without context.
+ *
+ * @throws JobExecutionException
+ */
+ @Test
+ public void testExecuteWithoutContext() throws JobExecutionException
+ {
+ expected.expect(NullPointerException.class);
+ expected.expectMessage("execution context");
+ new InjectionEnabledJob().execute(null);
+ }
+
+ /**
+ * Tests {@link InjectionEnabledJob#execute(org.quartz.JobExecutionContext)} without job detail.
+ *
+ * @throws JobExecutionException
+ */
+ @Test
+ public void testExecuteWithoutJobDetail() throws JobExecutionException
+ {
+ expected.expect(NullPointerException.class);
+ expected.expectMessage("detail");
+ new InjectionEnabledJob().execute(jec);
+ }
+
+ /**
+ * Tests {@link InjectionEnabledJob#execute(org.quartz.JobExecutionContext)} without data map.
+ *
+ * @throws JobExecutionException
+ */
+ @Test
+ public void testExecuteWithoutDataMap() throws JobExecutionException
+ {
+ when(jec.getJobDetail()).thenReturn(detail);
+ expected.expect(NullPointerException.class);
+ expected.expectMessage("data map");
+ new InjectionEnabledJob().execute(jec);
+ }
+
+ /**
+ * Tests {@link InjectionEnabledJob#execute(org.quartz.JobExecutionContext)} without injector.
+ *
+ * @throws JobExecutionException
+ */
+ @Test
+ public void testExecuteWithoutInjector() throws JobExecutionException
+ {
+ when(jec.getJobDetail()).thenReturn(detail);
+ when(detail.getJobDataMap()).thenReturn(dataMap);
+ expected.expect(NullPointerException.class);
+ expected.expectMessage("injector");
+ new InjectionEnabledJob().execute(jec);
+ }
+
+ /**
+ * Tests {@link InjectionEnabledJob#execute(org.quartz.JobExecutionContext)} without runnable.
+ *
+ * @throws JobExecutionException
+ */
+ @Test
+ public void testExecuteWithoutRunnable() throws JobExecutionException
+ {
+ when(jec.getJobDetail()).thenReturn(detail);
+ when(detail.getJobDataMap()).thenReturn(dataMap);
+ when(dataMap.get(Injector.class.getName())).thenReturn(injector);
+ expected.expect(JobExecutionException.class);
+ expected.expectMessage("runnable");
+ new InjectionEnabledJob().execute(jec);
+ }
+
+ /**
+ * Tests {@link InjectionEnabledJob#execute(org.quartz.JobExecutionContext)}.
+ *
+ * @throws JobExecutionException
+ */
+ @Test
+ public void testExecute() throws JobExecutionException
+ {
+ when(jec.getJobDetail()).thenReturn(detail);
+ when(detail.getJobDataMap()).thenReturn(dataMap);
+ when(dataMap.get(Injector.class.getName())).thenReturn(injector);
+ when(dataMap.get(Runnable.class.getName())).thenReturn(runnable);
+ when(injector.getInstance(AdministrationContext.class)).thenReturn(context);
+ new InjectionEnabledJob().execute(jec);
+ verify(context).runAsAdmin(Mockito.any(PrivilegedAction.class));
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/schedule/QuartzSchedulerTest.java b/scm-webapp/src/test/java/sonia/scm/schedule/QuartzSchedulerTest.java
new file mode 100644
index 0000000000..2f3dc86ea1
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/schedule/QuartzSchedulerTest.java
@@ -0,0 +1,220 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.schedule;
+
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.hamcrest.Matchers.*;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.quartz.CronTrigger;
+import org.quartz.JobDetail;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+
+/**
+ * Unit tests for {@link QuartzScheduler}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class QuartzSchedulerTest {
+
+ @Mock
+ private Injector injector;
+
+ @Mock
+ private org.quartz.Scheduler quartzScheduler;
+
+ private QuartzScheduler scheduler;
+
+ @Before
+ public void setUp()
+ {
+ scheduler = new QuartzScheduler(injector, quartzScheduler);
+ }
+
+ /**
+ * Tests {@link QuartzScheduler#schedule(java.lang.String, java.lang.Runnable)}.
+ *
+ * @throws SchedulerException
+ */
+ @Test
+ public void testSchedule() throws SchedulerException
+ {
+ DummyRunnable dr = new DummyRunnable();
+ Task task = scheduler.schedule("42 2 * * * ?", dr);
+ assertNotNull(task);
+
+ ArgumentCaptor detailCaptor = ArgumentCaptor.forClass(JobDetail.class);
+ ArgumentCaptor triggerCaptor = ArgumentCaptor.forClass(Trigger.class);
+ verify(quartzScheduler).scheduleJob(detailCaptor.capture(), triggerCaptor.capture());
+
+ Trigger trigger = triggerCaptor.getValue();
+ assertThat(trigger, is(instanceOf(CronTrigger.class)));
+ CronTrigger cron = (CronTrigger) trigger;
+ assertEquals("42 2 * * * ?", cron.getCronExpression());
+
+ JobDetail detail = detailCaptor.getValue();
+ assertEquals(InjectionEnabledJob.class, detail.getJobClass());
+ Provider runnable = (Provider) detail.getJobDataMap().get(Runnable.class.getName());
+ assertNotNull(runnable);
+ assertSame(dr, runnable.get());
+ assertEquals(injector, detail.getJobDataMap().get(Injector.class.getName()));
+ }
+
+ /**
+ * Tests {@link QuartzScheduler#schedule(java.lang.String, java.lang.Class)}.
+ *
+ * @throws SchedulerException
+ */
+ @Test
+ public void testScheduleWithClass() throws SchedulerException
+ {
+ scheduler.schedule("42 * * * * ?", DummyRunnable.class);
+
+ verify(injector).getProvider(DummyRunnable.class);
+
+ ArgumentCaptor detailCaptor = ArgumentCaptor.forClass(JobDetail.class);
+ ArgumentCaptor triggerCaptor = ArgumentCaptor.forClass(Trigger.class);
+ verify(quartzScheduler).scheduleJob(detailCaptor.capture(), triggerCaptor.capture());
+
+ Trigger trigger = triggerCaptor.getValue();
+ assertThat(trigger, is(instanceOf(CronTrigger.class)));
+ CronTrigger cron = (CronTrigger) trigger;
+ assertEquals("42 * * * * ?", cron.getCronExpression());
+
+ JobDetail detail = detailCaptor.getValue();
+ assertEquals(InjectionEnabledJob.class, detail.getJobClass());
+ assertEquals(injector, detail.getJobDataMap().get(Injector.class.getName()));
+ }
+
+ /**
+ * Tests {@link QuartzScheduler#init(sonia.scm.SCMContextProvider)}.
+ *
+ * @throws SchedulerException
+ */
+ @Test
+ public void testInit() throws SchedulerException
+ {
+ when(quartzScheduler.isStarted()).thenReturn(Boolean.FALSE);
+ scheduler.init(null);
+ verify(quartzScheduler).start();
+ }
+
+ /**
+ * Tests {@link QuartzScheduler#init(sonia.scm.SCMContextProvider)} when the underlying scheduler is already started.
+ *
+ * @throws SchedulerException
+ */
+ @Test
+ public void testInitAlreadyRunning() throws SchedulerException
+ {
+ when(quartzScheduler.isStarted()).thenReturn(Boolean.TRUE);
+ scheduler.init(null);
+ verify(quartzScheduler, never()).start();
+ }
+
+ /**
+ * Tests {@link QuartzScheduler#init(sonia.scm.SCMContextProvider)} when the underlying scheduler throws an exception.
+ *
+ * @throws SchedulerException
+ */
+ @Test
+ public void testInitException() throws SchedulerException
+ {
+ when(quartzScheduler.isStarted()).thenThrow(SchedulerException.class);
+ scheduler.init(null);
+ verify(quartzScheduler, never()).start();
+ }
+
+ /**
+ * Tests {@link QuartzScheduler#close()}.
+ *
+ * @throws IOException
+ * @throws SchedulerException
+ */
+ @Test
+ public void testClose() throws IOException, SchedulerException
+ {
+ when(quartzScheduler.isStarted()).thenReturn(Boolean.TRUE);
+ scheduler.close();
+ verify(quartzScheduler).shutdown();
+ }
+
+ /**
+ * Tests {@link QuartzScheduler#close()} when the underlying scheduler is not running.
+ *
+ * @throws IOException
+ * @throws SchedulerException
+ */
+ @Test
+ public void testCloseNotRunning() throws IOException, SchedulerException
+ {
+ when(quartzScheduler.isStarted()).thenReturn(Boolean.FALSE);
+ scheduler.close();
+ verify(quartzScheduler, never()).shutdown();
+ }
+
+ /**
+ * Tests {@link QuartzScheduler#close()} when the underlying scheduler throws an exception.
+ *
+ * @throws IOException
+ * @throws SchedulerException
+ */
+ @Test
+ public void testCloseException() throws IOException, SchedulerException
+ {
+ when(quartzScheduler.isStarted()).thenThrow(SchedulerException.class);
+ scheduler.close();
+ verify(quartzScheduler, never()).shutdown();
+ }
+
+
+ public static class DummyRunnable implements Runnable {
+
+ @Override
+ public void run()
+ {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/schedule/QuartzTaskTest.java b/scm-webapp/src/test/java/sonia/scm/schedule/QuartzTaskTest.java
new file mode 100644
index 0000000000..5ba5c19373
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/schedule/QuartzTaskTest.java
@@ -0,0 +1,89 @@
+/***
+ * Copyright (c) 2015, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * https://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.schedule;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.hamcrest.Matchers.*;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.quartz.JobKey;
+import org.quartz.SchedulerException;
+
+/**
+ * Unit tests for {@link QuartzTask}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class QuartzTaskTest {
+
+ @Mock
+ private org.quartz.Scheduler scheduler;
+
+ private final JobKey jobKey = new JobKey("sample");
+
+ private QuartzTask task;
+
+ @Before
+ public void setUp(){
+ task = new QuartzTask(scheduler, jobKey);
+ }
+
+ /**
+ * Tests {@link QuartzTask#cancel()}.
+ *
+ * @throws SchedulerException
+ */
+ @Test
+ public void testCancel() throws SchedulerException
+ {
+ task.cancel();
+ verify(scheduler).deleteJob(jobKey);
+ }
+
+ /**
+ * Tests {@link QuartzTask#cancel()} when the scheduler throws an exception.
+ * @throws org.quartz.SchedulerException
+ */
+ @Test(expected = RuntimeException.class)
+ public void testCancelWithException() throws SchedulerException
+ {
+ when(scheduler.deleteJob(jobKey)).thenThrow(SchedulerException.class);
+ task.cancel();
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java
new file mode 100644
index 0000000000..edec33aa59
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.security;
+
+import com.github.sdorra.shiro.ShiroRule;
+import com.github.sdorra.shiro.SubjectAware;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.authz.permission.PermissionResolver;
+import org.apache.shiro.authz.permission.WildcardPermission;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import static org.mockito.Mockito.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.HandlerEventType;
+import sonia.scm.cache.Cache;
+import sonia.scm.cache.CacheManager;
+import sonia.scm.group.Group;
+import sonia.scm.group.GroupEvent;
+import sonia.scm.group.GroupModificationEvent;
+import sonia.scm.group.GroupNames;
+import sonia.scm.repository.PermissionType;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryDAO;
+import sonia.scm.repository.RepositoryEvent;
+import sonia.scm.repository.RepositoryModificationEvent;
+import sonia.scm.repository.RepositoryTestData;
+import sonia.scm.user.User;
+import sonia.scm.user.UserEvent;
+import sonia.scm.user.UserModificationEvent;
+import sonia.scm.user.UserTestData;
+
+/**
+ * Unit tests for {@link AuthorizationCollector}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultAuthorizationCollectorTest {
+
+ @Mock
+ private Cache cache;
+
+ @Mock
+ private CacheManager cacheManager;
+
+ @Mock
+ private RepositoryDAO repositoryDAO;
+
+ @Mock
+ private SecuritySystem securitySystem;
+
+ private DefaultAuthorizationCollector collector;
+
+ @Rule
+ public ShiroRule shiro = new ShiroRule();
+
+ /**
+ * Set up object to test.
+ */
+ @Before
+ public void setUp(){
+ when(cacheManager.getCache(Mockito.any(String.class))).thenReturn(cache);
+
+ collector = new DefaultAuthorizationCollector(cacheManager, repositoryDAO, securitySystem);
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#onEvent(sonia.scm.user.UserEvent)}.
+ */
+ @Test
+ public void testOnUserEvent()
+ {
+ User user = UserTestData.createDent();
+ collector.onEvent(new UserEvent(HandlerEventType.BEFORE_CREATE, user));
+ verify(cache, never()).clear();
+
+ collector.onEvent(new UserEvent(HandlerEventType.CREATE, user));
+ verify(cache).removeAll(Mockito.any(Predicate.class));
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#onEvent(sonia.scm.user.UserEvent)} with modified user.
+ */
+ @Test
+ public void testOnUserModificationEvent()
+ {
+ User user = UserTestData.createDent();
+ User userModified = UserTestData.createDent();
+ userModified.setDisplayName("Super Dent");
+
+ collector.onEvent(new UserModificationEvent(HandlerEventType.BEFORE_CREATE, userModified, user));
+ verify(cache, never()).removeAll(Mockito.any(Predicate.class));
+
+ collector.onEvent(new UserModificationEvent(HandlerEventType.CREATE, userModified, user));
+ verify(cache, never()).removeAll(Mockito.any(Predicate.class));
+
+ userModified.setAdmin(true);
+
+ collector.onEvent(new UserModificationEvent(HandlerEventType.BEFORE_CREATE, userModified, user));
+ verify(cache, never()).removeAll(Mockito.any(Predicate.class));
+
+ collector.onEvent(new UserModificationEvent(HandlerEventType.CREATE, userModified, user));
+ verify(cache).removeAll(Mockito.any(Predicate.class));
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#onEvent(sonia.scm.group.GroupEvent)}.
+ */
+ @Test
+ public void testOnGroupEvent()
+ {
+ Group group = new Group("xml", "base");
+ collector.onEvent(new GroupEvent(HandlerEventType.BEFORE_CREATE, group));
+ verify(cache, never()).clear();
+
+ collector.onEvent(new GroupEvent(HandlerEventType.CREATE, group));
+ verify(cache).clear();
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#onEvent(sonia.scm.group.GroupEvent)} with modified groups.
+ */
+ @Test
+ public void testOnGroupModificationEvent()
+ {
+ Group group = new Group("xml", "base");
+ Group modifiedGroup = new Group("xml", "base");
+ collector.onEvent(new GroupModificationEvent(HandlerEventType.BEFORE_MODIFY, modifiedGroup, group));
+ verify(cache, never()).clear();
+
+ collector.onEvent(new GroupModificationEvent(HandlerEventType.MODIFY, modifiedGroup, group));
+ verify(cache, never()).clear();
+
+ modifiedGroup.add("test");
+ collector.onEvent(new GroupModificationEvent(HandlerEventType.MODIFY, modifiedGroup, group));
+ verify(cache).clear();
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#onEvent(sonia.scm.repository.RepositoryEvent)}.
+ */
+ @Test
+ public void testOnRepositoryEvent()
+ {
+ Repository repository = RepositoryTestData.createHeartOfGold();
+ collector.onEvent(new RepositoryEvent(HandlerEventType.BEFORE_CREATE, repository));
+ verify(cache, never()).clear();
+
+ collector.onEvent(new RepositoryEvent(HandlerEventType.CREATE, repository));
+ verify(cache).clear();
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#onEvent(sonia.scm.repository.RepositoryEvent)} with modified repository.
+ */
+ @Test
+ public void testOnRepositoryModificationEvent()
+ {
+ Repository repositoryModified = RepositoryTestData.createHeartOfGold();
+ repositoryModified.setName("test123");
+ repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
+
+ Repository repository = RepositoryTestData.createHeartOfGold();
+ repository.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
+
+ collector.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository));
+ verify(cache, never()).clear();
+
+ collector.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
+ verify(cache, never()).clear();
+
+ repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
+ collector.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
+ verify(cache, never()).clear();
+
+ repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test123")));
+ collector.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
+ verify(cache).clear();
+
+ repositoryModified.setPermissions(
+ Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.READ, true))
+ );
+ collector.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
+ verify(cache, times(2)).clear();
+
+ repositoryModified.setPermissions(
+ Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.WRITE))
+ );
+ collector.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
+ verify(cache, times(3)).clear();
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#onEvent(sonia.scm.security.StoredAssignedPermissionEvent)}.
+ */
+ @Test
+ public void testOnStoredAssignedPermissionEvent()
+ {
+ StoredAssignedPermission groupPermission = new StoredAssignedPermission(
+ "123", new AssignedPermission("_authenticated", true, "repository:read:*")
+ );
+ collector.onEvent(new StoredAssignedPermissionEvent(HandlerEventType.BEFORE_CREATE, groupPermission));
+ verify(cache, never()).clear();
+
+ collector.onEvent(new StoredAssignedPermissionEvent(HandlerEventType.CREATE, groupPermission));
+ verify(cache).clear();
+
+
+ StoredAssignedPermission userPermission = new StoredAssignedPermission(
+ "123", new AssignedPermission("trillian", false, "repository:read:*")
+ );
+ collector.onEvent(new StoredAssignedPermissionEvent(HandlerEventType.BEFORE_CREATE, userPermission));
+ verify(cache, never()).removeAll(Mockito.any(Predicate.class));
+ verify(cache).clear();
+
+ collector.onEvent(new StoredAssignedPermissionEvent(HandlerEventType.CREATE, userPermission));
+ verify(cache).removeAll(Mockito.any(Predicate.class));
+ verify(cache).clear();
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#collect()} without user role.
+ */
+ @Test
+ @SubjectAware
+ public void testCollectWithoutUserRole()
+ {
+ AuthorizationInfo authInfo = collector.collect();
+ assertThat(authInfo.getRoles(), nullValue());
+ assertThat(authInfo.getStringPermissions(), nullValue());
+ assertThat(authInfo.getObjectPermissions(), nullValue());
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#collect()} from cache.
+ */
+ @Test
+ @SubjectAware(
+ configuration = "classpath:sonia/scm/shiro-001.ini"
+ )
+ public void testCollectFromCache()
+ {
+ AuthorizationInfo info = new SimpleAuthorizationInfo();
+ when(cache.get(anyObject())).thenReturn(info);
+ authenticate(UserTestData.createTrillian(), "main");
+
+ AuthorizationInfo authInfo = collector.collect();
+ assertSame(info, authInfo);
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#collect()} with cache.
+ */
+ @Test
+ @SubjectAware(
+ configuration = "classpath:sonia/scm/shiro-001.ini"
+ )
+ public void testCollectWithCache(){
+ authenticate(UserTestData.createTrillian(), "main");
+
+ AuthorizationInfo authInfo = collector.collect();
+ verify(cache).put(any(), any());
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#collect()} without permissions.
+ */
+ @Test
+ @SubjectAware(
+ configuration = "classpath:sonia/scm/shiro-001.ini"
+ )
+ public void testCollectWithoutPermissions()
+ {
+ authenticate(UserTestData.createTrillian(), "main");
+
+ AuthorizationInfo authInfo = collector.collect();
+ assertThat(authInfo.getRoles(), Matchers.contains(Role.USER));
+ assertThat(authInfo.getStringPermissions(), hasSize(0));
+ assertThat(authInfo.getObjectPermissions(), nullValue());
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#collect()} as admin.
+ */
+ @Test
+ @SubjectAware(
+ configuration = "classpath:sonia/scm/shiro-001.ini"
+ )
+ public void testCollectAsAdmin()
+ {
+ User trillian = UserTestData.createTrillian();
+ trillian.setAdmin(true);
+ authenticate(trillian, "main");
+
+ AuthorizationInfo authInfo = collector.collect();
+ assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER, Role.ADMIN));
+ assertThat(authInfo.getObjectPermissions(), nullValue());
+ assertThat(authInfo.getStringPermissions(), Matchers.contains("*"));
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#collect()} with repository permissions.
+ */
+ @Test
+ @SubjectAware(
+ configuration = "classpath:sonia/scm/shiro-001.ini"
+ )
+ public void testCollectWithRepositoryPermissions()
+ {
+ String group = "heart-of-gold-crew";
+ authenticate(UserTestData.createTrillian(), group);
+ Repository heartOfGold = RepositoryTestData.createHeartOfGold();
+ heartOfGold.setId("one");
+ heartOfGold.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("trillian")));
+ Repository puzzle42 = RepositoryTestData.create42Puzzle();
+ puzzle42.setId("two");
+ sonia.scm.repository.Permission permission = new sonia.scm.repository.Permission(group, PermissionType.WRITE, true);
+ puzzle42.setPermissions(Lists.newArrayList(permission));
+ when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42));
+
+ // execute and assert
+ AuthorizationInfo authInfo = collector.collect();
+ assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
+ assertThat(authInfo.getObjectPermissions(), nullValue());
+ assertThat(authInfo.getStringPermissions(), containsInAnyOrder("repository:read:one", "repository:read,write:two"));
+ }
+
+ /**
+ * Tests {@link AuthorizationCollector#collect()} with global permissions.
+ */
+ @Test
+ @SubjectAware(
+ configuration = "classpath:sonia/scm/shiro-001.ini"
+ )
+ public void testCollectWithGlobalPermissions(){
+ authenticate(UserTestData.createTrillian(), "main");
+
+ StoredAssignedPermission p1 = new StoredAssignedPermission("one", new AssignedPermission("one", "one:one"));
+ StoredAssignedPermission p2 = new StoredAssignedPermission("two", new AssignedPermission("two", "two:two"));
+ when(securitySystem.getPermissions(Mockito.any(Predicate.class))).thenReturn(Lists.newArrayList(p1, p2));
+
+ // execute and assert
+ AuthorizationInfo authInfo = collector.collect();
+ assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
+ assertThat(authInfo.getObjectPermissions(), nullValue());
+ assertThat(authInfo.getStringPermissions(), containsInAnyOrder("one:one", "two:two"));
+ }
+
+ private void authenticate(User user, String group, String... groups)
+ {
+ SimplePrincipalCollection spc = new SimplePrincipalCollection();
+ spc.add(user.getName(), "unit");
+ spc.add(user, "unit");
+ spc.add(new GroupNames(group, groups), "unit");
+ Subject subject = new Subject.Builder().authenticated(true).principals(spc).buildSubject();
+ shiro.setSubject(subject);
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/security/XsrfCookiesTest.java b/scm-webapp/src/test/java/sonia/scm/security/XsrfCookiesTest.java
new file mode 100644
index 0000000000..64dd27bc3b
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/security/XsrfCookiesTest.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.security;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ * Tests for the {@link XsrfCookies} util class.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class XsrfCookiesTest {
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ /**
+ * Prepare mocks for testing.
+ */
+ @Before
+ public void prepareMocks(){
+ when(request.getContextPath()).thenReturn("/scm");
+ }
+
+ /**
+ * Tests create method.
+ */
+ @Test
+ public void testCreate()
+ {
+ XsrfCookies.create(request, response, "mytoken");
+
+ // capture cookie
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Cookie.class);
+ verify(response).addCookie(captor.capture());
+
+ // check for cookie
+ Cookie cookie = captor.getValue();
+ assertEquals(XsrfProtectionFilter.KEY, cookie.getName());
+ assertEquals("/scm", cookie.getPath());
+ assertEquals("mytoken", cookie.getValue());
+ }
+
+ /**
+ * Tests remove method.
+ */
+ @Test
+ public void testRemove(){
+ Cookie cookie = new Cookie(XsrfProtectionFilter.KEY, "mytoken");
+ cookie.setMaxAge(15);
+ when(request.getCookies()).thenReturn(new Cookie[]{cookie});
+ XsrfCookies.remove(request, response);
+
+ // capture cookie
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Cookie.class);
+ verify(response).addCookie(captor.capture());
+
+ // check the captured cookie
+ Cookie c = captor.getValue();
+ assertEquals("cookie max age should be set to 0", 0, c.getMaxAge());
+ assertEquals("cookie path should be equals", cookie.getPath(), c.getPath());
+ assertNull("cookie value shuld be null", c.getValue());
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/security/XsrfProtectionFilterTest.java b/scm-webapp/src/test/java/sonia/scm/security/XsrfProtectionFilterTest.java
new file mode 100644
index 0000000000..0a2145de04
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/security/XsrfProtectionFilterTest.java
@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+package sonia.scm.security;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import static org.mockito.Mockito.*;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.config.ScmConfiguration;
+import sonia.scm.util.HttpUtil;
+
+/**
+ * Unit tests for {@link XsrfProtectionFilter}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class XsrfProtectionFilterTest {
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ @Mock
+ private HttpSession session;
+
+ @Mock
+ private FilterChain chain;
+
+ private final ScmConfiguration configuration = new ScmConfiguration();
+
+ private final XsrfProtectionFilter filter = new XsrfProtectionFilter(configuration);
+
+ /**
+ * Prepare mocks for testing.
+ */
+ @Before
+ public void setUp(){
+ when(request.getSession(true)).thenReturn(session);
+ when(request.getContextPath()).thenReturn("/scm");
+ configuration.setEnabledXsrfProtection(true);
+ }
+
+ /**
+ * Test filter method for non web interface clients.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterFromNonWuiClient() throws IOException, ServletException
+ {
+ filter.doFilter(request, response, chain);
+ verify(chain).doFilter(request, response);
+ }
+
+ /**
+ * Test filter method with disabled xsrf protection.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterWithDisabledXsrfProtection() throws IOException, ServletException
+ {
+ // disable xsrf protection
+ configuration.setEnabledXsrfProtection(false);
+
+ // set webui user-agent
+ when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
+
+ // call the filter
+ filter.doFilter(request, response, chain);
+
+ // verify that no xsrf other any other cookie was set
+ verify(response, never()).addCookie(Mockito.any(Cookie.class));
+
+ // ensure filter chain is called
+ verify(chain).doFilter(request, response);
+ }
+
+ /**
+ * Test filter method for first web interface request.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterIssuesTokenOnFirstWuiRequest() throws IOException, ServletException
+ {
+ when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
+
+ // call the filter
+ filter.doFilter(request, response, chain);
+
+ // capture cookie
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Cookie.class);
+ verify(response).addCookie(captor.capture());
+
+ // check for cookie
+ Cookie cookie = captor.getValue();
+ assertEquals(XsrfProtectionFilter.KEY, cookie.getName());
+ assertEquals("/scm", cookie.getPath());
+ assertNotNull(cookie.getValue());
+
+ // ensure filter chain is called
+ verify(chain).doFilter(request, response);
+ }
+
+ /**
+ * Test filter method on protected session with an invalid xsrf token.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterWithInvalidToken() throws IOException, ServletException {
+ when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
+ when(request.getHeader(XsrfProtectionFilter.KEY)).thenReturn("invalidtoken");
+ when(session.getAttribute(XsrfProtectionFilter.KEY)).thenReturn("mytoken");
+
+ // call the filter
+ filter.doFilter(request, response, chain);
+
+ // ensure response send forbidden and the chain was never called
+ verify(response).sendError(HttpServletResponse.SC_FORBIDDEN);
+ verify(chain, never()).doFilter(request, response);
+ }
+
+ /**
+ * Test filter method on protected session without xsrf token.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterOnProtectedSessionWithoutToken() throws IOException, ServletException {
+ when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
+ when(session.getAttribute(XsrfProtectionFilter.KEY)).thenReturn("mytoken");
+
+ // call the filter
+ filter.doFilter(request, response, chain);
+
+ // ensure response send forbidden and the chain was never called
+ verify(response).sendError(HttpServletResponse.SC_FORBIDDEN);
+ verify(chain, never()).doFilter(request, response);
+ }
+
+ /**
+ * Test filter method on protected session with valid xsrf token.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterOnProtectedSessionWithValidToken() throws IOException, ServletException {
+ when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI);
+ when(request.getHeader(XsrfProtectionFilter.KEY)).thenReturn("mytoken");
+ when(session.getAttribute(XsrfProtectionFilter.KEY)).thenReturn("mytoken");
+
+ // call the filter
+ filter.doFilter(request, response, chain);
+
+ // ensure chain was called
+ verify(chain).doFilter(request, response);
+ }
+
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/AuthenticationITCase.java b/scm-webapp/src/test/java/sonia/scm/selenium/AuthenticationITCase.java
index a2b0d0fe6d..34013e7e76 100644
--- a/scm-webapp/src/test/java/sonia/scm/selenium/AuthenticationITCase.java
+++ b/scm-webapp/src/test/java/sonia/scm/selenium/AuthenticationITCase.java
@@ -34,25 +34,27 @@ package sonia.scm.selenium;
//~--- non-JDK imports --------------------------------------------------------
+import sonia.scm.selenium.page.Pages;
+import sonia.scm.selenium.page.MainPage;
+import sonia.scm.selenium.page.LoginPage;
+import static org.junit.Assert.*;
import org.junit.Test;
/**
- *
+ * Authentication related selenium integration tests.
+ *
* @author Sebastian Sdorra
*/
-public class AuthenticationITCase extends SeleniumTestBase
-{
+public class AuthenticationITCase extends SeleniumITCaseBase {
/**
- * Method description
- *
- *
- * @throws Exception
+ * Authenticates an user and call logout function.
*/
@Test
- public void testAuthentication() throws Exception
- {
- login("scmadmin", "scmadmin");
- logout();
+ public void testAuthentication() {
+ MainPage main = Pages.get(driver, LoginPage.class).login("scmadmin", "scmadmin");
+ assertEquals("scmadmin", main.getUserInfo());
+ main.logout();
}
+
}
diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/RepositoryCRUDITCase.java b/scm-webapp/src/test/java/sonia/scm/selenium/RepositoriesITCase.java
similarity index 66%
rename from scm-webapp/src/test/java/sonia/scm/selenium/RepositoryCRUDITCase.java
rename to scm-webapp/src/test/java/sonia/scm/selenium/RepositoriesITCase.java
index 64760ae774..05898f6529 100644
--- a/scm-webapp/src/test/java/sonia/scm/selenium/RepositoryCRUDITCase.java
+++ b/scm-webapp/src/test/java/sonia/scm/selenium/RepositoriesITCase.java
@@ -34,61 +34,55 @@ package sonia.scm.selenium;
//~--- non-JDK imports --------------------------------------------------------
+import sonia.scm.selenium.page.Pages;
+import sonia.scm.selenium.page.MainPage;
+import sonia.scm.selenium.page.LoginPage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import static org.junit.Assert.*;
+import sonia.scm.repository.Repository;
/**
- *
+ * Repository related selenium integration tests.
+ *
* @author Sebastian Sdorra
*/
-public class RepositoryCRUDITCase extends SeleniumTestBase
-{
+public class RepositoriesITCase extends SeleniumITCaseBase {
+ private MainPage main;
+
/**
- * Method description
- *
- */
- @After
- public void after()
- {
- logout();
- }
-
- /**
- * Method description
- *
- */
- @Test
- public void createRepository() throws InterruptedException
- {
- waitAndClick("#repositoryAddButton");
- waitForPresence("input[name=name]").sendKeys("scm");
- select("#x-form-el-repositoryType img").click();
- waitAndClick("div.x-combo-list-item:nth-of-type(2)");
- type("input[name=contact]", "scmadmin@scm-manager.org");
- type("textarea[name=description]", "SCM-Manager");
- waitAndClick("div.x-panel-btns button:nth-of-type(1)");
-
- String name =
- waitForPresence(
- "div.x-grid3-row-selected div.x-grid3-col-name").getText();
-
- assertEquals("scm", name);
-
- waitAndClick("#repoRmButton button");
- waitAndClick("div.x-window button:nth-of-type(1)");
- }
-
- /**
- * Method description
- *
+ * Authenticates admin user, before each test.
*/
@Before
- public void login()
- {
- login("scmadmin", "scmadmin");
+ public void login() {
+ main = Pages.get(driver, LoginPage.class)
+ .login("scmadmin", "scmadmin");
+ }
+
+ /**
+ * Creates, select and removes a repository.
+ */
+ @Test
+ public void createRepository() {
+ Repository repository = new Repository();
+ repository.setName("scm");
+ repository.setType("git");
+ repository.setContact("scmadmin@scm-manager.org");
+ repository.setDescription("SCM-Manager");
+
+ main.repositories()
+ .add(repository)
+ .select(repository.getName())
+ .remove();
+ }
+
+ /**
+ * Logs the user out, after each test.
+ */
+ @After
+ public void logout() {
+ main.logout();
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumITCaseBase.java b/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumITCaseBase.java
new file mode 100644
index 0000000000..fde04d9cad
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumITCaseBase.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.selenium;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.firefox.FirefoxDriver;
+
+/**
+ * Base class for selenium integration tests.
+ *
+ * @author Sebastian Sdorra
+ */
+public class SeleniumITCaseBase {
+
+ /**
+ * Selenium test driver.
+ */
+ protected static WebDriver driver;
+
+ /**
+ * Setup selenium test driver.
+ */
+ @BeforeClass
+ public static void setUpDriver() {
+ // DesiredCapabilities capa = DesiredCapabilities.chrome();
+ // capa.setBrowserName("firefox");
+ // capa.setPlatform(Platform.ANY);
+ // RemoteWebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), capa);
+
+ driver = new FirefoxDriver();
+ driver.get("http://localhost:8082/scm/index.html");
+ }
+
+ /**
+ * Closes the selenium test driver.
+ */
+ @AfterClass
+ public static void tearDownDriver() {
+ driver.close();
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumTestBase.java b/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumTestBase.java
deleted file mode 100644
index 1ae5e86b3d..0000000000
--- a/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumTestBase.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/**
- * Copyright (c) 2010, Sebastian Sdorra
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * 3. Neither the name of SCM-Manager; nor the names of its
- * contributors may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * http://bitbucket.org/sdorra/scm-manager
- *
- */
-
-
-package sonia.scm.selenium;
-
-//~--- non-JDK imports --------------------------------------------------------
-
-import com.google.common.io.Files;
-
-import org.junit.After;
-import org.junit.Before;
-
-import org.openqa.selenium.By;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.firefox.FirefoxDriver;
-import org.openqa.selenium.support.ui.ExpectedConditions;
-import org.openqa.selenium.support.ui.WebDriverWait;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.junit.Assert.*;
-
-//~--- JDK imports ------------------------------------------------------------
-
-import java.io.File;
-import java.io.IOException;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- *
- * @author Sebastian Sdorra
- */
-public class SeleniumTestBase
-{
-
- /**
- * the logger for SeleniumTestBase
- */
- private static final Logger logger =
- LoggerFactory.getLogger(SeleniumTestBase.class);
-
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @throws Exception
- */
- @After
- public void tearDown() throws Exception
- {
- driver.quit();
- }
-
- //~--- set methods ----------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @throws Exception
- */
- @Before
- public void setUp() throws Exception
- {
- driver = new FirefoxDriver();
- baseUrl = "http://localhost:8082/scm/";
- open("index.html");
- }
-
- //~--- methods --------------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param username
- * @param password
- */
- protected void login(String username, String password)
- {
- type("input[name=username]", username);
- type("input[name=password]", password);
- waitAndClick("#loginButton button");
-
- String ue = waitForPresence("#scm-userinfo-tip").getText();
-
- assertEquals(username, ue);
- }
-
- /**
- * Method description
- *
- */
- protected void logout()
- {
- waitAndClick("#navLogout a");
- }
-
- /**
- * Method description
- *
- *
- * @param url
- */
- protected void open(String url)
- {
- driver.get(baseUrl + url);
- pause(500, TimeUnit.MILLISECONDS);
- }
-
- /**
- * Method description
- *
- *
- * @param value
- * @param unit
- */
- protected void pause(int value, TimeUnit unit)
- {
- driver.manage().timeouts().implicitlyWait(value, unit);
- }
-
- /**
- * Method description
- *
- *
- * @param target
- */
- protected void screenshot(String target)
- {
- screenshot(new File(target));
- }
-
- /**
- * Method description
- *
- *
- * @param target
- */
- protected void screenshot(File target)
- {
- try
- {
- File scrFile =
- ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
-
- Files.copy(scrFile, target);
- }
- catch (IOException ex)
- {
- logger.error("could not create screenshot", ex);
- }
- }
-
- /**
- * Method description
- *
- *
- * @param cssSelector
- *
- * @return
- */
- protected WebElement select(String cssSelector)
- {
- WebElement element = driver.findElement(By.cssSelector(cssSelector));
-
- assertNotNull(element);
-
- return element;
- }
-
- /**
- * Method description
- *
- *
- * @param cssLocator
- * @param value
- */
- protected void type(String cssLocator, String value)
- {
- select(cssLocator).clear();
- select(cssLocator).sendKeys(value);
- }
-
- /**
- * Method description
- *
- *
- * @param query
- */
- protected void waitAndClick(String query)
- {
- waitToBeClickable(query).click();
- }
-
- /**
- * Method description
- *
- *
- * @param query
- *
- * @return
- */
- protected WebElement waitForPresence(String query)
- {
- WebDriverWait wait = new WebDriverWait(driver, 5);
-
- return wait.until(
- ExpectedConditions.presenceOfElementLocated(By.cssSelector(query)));
- }
-
- /**
- * Method description
- *
- *
- * @param query
- *
- * @return
- */
- protected WebElement waitToBeClickable(String query)
- {
- WebDriverWait wait = new WebDriverWait(driver, 5);
-
- return wait.until(
- ExpectedConditions.elementToBeClickable(By.cssSelector(query)));
- }
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- protected WebDriver driver;
-
- /** Field description */
- private String baseUrl;
-}
diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/BasePage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/BasePage.java
new file mode 100644
index 0000000000..28c27457a8
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/BasePage.java
@@ -0,0 +1,168 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.selenium.page;
+
+import com.google.common.base.Throwables;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.openqa.selenium.By;
+import org.openqa.selenium.OutputType;
+import org.openqa.selenium.TakesScreenshot;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+/**
+ * Abstract selenium base page.
+ *
+ * @author Sebastian Sdorra
+ *
+ * @param
concrete page implementation
+ */
+public abstract class BasePage
{
+
+ /**
+ * Selenium test driver.
+ */
+ protected final WebDriver driver;
+
+ /**
+ * Constructs a new base page.
+ *
+ * @param driver selenium test driver
+ */
+ protected BasePage(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ /**
+ * Performs a {@link Thread#sleep(long)} for the given timeout.
+ *
+ * @param time timeout
+ * @param unit time unit of timeout
+ */
+ protected void sleep(long time, TimeUnit unit) {
+ try {
+ unit.sleep(time);
+ } catch (InterruptedException ex) {
+ throw Throwables.propagate(ex);
+ }
+ }
+
+ /**
+ * Wait for the element until it is clickable.
+ *
+ * @param by element selector
+ *
+ * @return web element
+ */
+ protected WebElement waitToBeClickable(By by){
+ return waitToBeClickable(driver.findElement(by));
+ }
+
+ /**
+ * Waits for the element until it is clickable.
+ *
+ * @param element web element
+ *
+ * @return web element
+ */
+ protected WebElement waitToBeClickable(WebElement element) {
+ WebDriverWait wait = new WebDriverWait(driver, 5);
+
+ return wait.until(ExpectedConditions.elementToBeClickable(element));
+ }
+
+ /**
+ * Waits until the element is present.
+ *
+ * @param by element locator
+ *
+ * @return web element
+ */
+ protected WebElement waitFor(By by){
+ WebDriverWait wait = new WebDriverWait(driver, 1);
+ return wait.until(ExpectedConditions.presenceOfElementLocated(by));
+ }
+
+ /**
+ * Waits until the elements are present.
+ *
+ * @param by element selector
+ *
+ * @return list of web elements
+ */
+ protected List waitForAll(By by){
+ WebDriverWait wait = new WebDriverWait(driver, 1);
+ return wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(by));
+ }
+
+ /**
+ * Creates a screenshot of the current browser content and stores it at the given path.
+ *
+ * @param target target file path
+ *
+ * @return {@code this}
+ */
+ public P screenshot(String target) {
+ return screenshot(new File(target));
+ }
+
+ /**
+ * Creates a screenshot of the current browser content and stores it at the file.
+ *
+ * @param target target file
+ *
+ * @return {@code this}
+ */
+ public P screenshot(File target) {
+ try {
+ File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
+
+ Files.copy(scrFile, target);
+ } catch (IOException ex) {
+ throw Throwables.propagate(ex);
+ }
+ return self();
+ }
+
+ /**
+ * Returns {@code this}.
+ *
+ * @return {@code this}
+ */
+ protected abstract P self();
+
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/LoginPage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/LoginPage.java
new file mode 100644
index 0000000000..cf9d231510
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/LoginPage.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.selenium.page;
+
+import java.util.concurrent.TimeUnit;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * Page object for the scm-manager login page.
+ *
+ * @author Sebastian Sdorra
+ */
+public class LoginPage extends BasePage {
+
+ @FindBy(css = "input[name=username]")
+ private WebElement usernameInput;
+
+ @FindBy(css = "input[name=password]")
+ private WebElement passwordInput;
+
+ @FindBy(css = "#loginButton button")
+ private WebElement loginButton;
+
+ /**
+ * Constructs a new page. This constructor should only be called from {@link Pages}.
+ *
+ * @param driver selenium test driver
+ */
+ LoginPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ protected LoginPage self() {
+ return this;
+ }
+
+ /**
+ * Authenticates the user and returns the {@link MainPage}.
+ *
+ * @param username username
+ * @param password password
+ *
+ * @return {@link MainPage} after successful authentication
+ */
+ public MainPage login(String username, String password) {
+ usernameInput.clear();
+ usernameInput.sendKeys(username);
+
+ passwordInput.clear();
+ passwordInput.sendKeys(password);
+
+ sleep(250, TimeUnit.MILLISECONDS);
+
+ waitToBeClickable(loginButton).click();
+
+ return Pages.get(driver, MainPage.class);
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/MainPage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/MainPage.java
new file mode 100644
index 0000000000..42fc807e67
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/MainPage.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.selenium.page;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * Page object for scm-manager's main page.
+ *
+ * @author Sebastian Sdorra
+ */
+public class MainPage extends BasePage {
+
+ @FindBy(css = "#navLogout a")
+ private WebElement logoutLink;
+
+ @FindBy(linkText = "Repositories")
+ private WebElement repositoriesLink;
+
+ @FindBy(css = "#scm-userinfo-tip")
+ private WebElement userInfoTip;
+
+ /**
+ * Constructs a new page. This constructor should only be called from {@link Pages}.
+ *
+ * @param driver selenium test driver
+ */
+ MainPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ protected MainPage self() {
+ return this;
+ }
+
+ /**
+ * Returns the name of the current authenticated user from the user info tip.
+ *
+ * @return name of the current authenticated user
+ */
+ public String getUserInfo(){
+ return userInfoTip.getText();
+ }
+
+ /**
+ * Navigates to the repositories page and returns the page object for this page.
+ *
+ * @return page object for repositories page
+ */
+ public RepositoriesPage repositories(){
+ repositoriesLink.click();
+ return Pages.get(driver, RepositoriesPage.class);
+ }
+
+ /**
+ * Logs the current user out.
+ *
+ * @return page object for the login
+ */
+ public LoginPage logout(){
+ waitToBeClickable(logoutLink).click();
+ return Pages.get(driver, LoginPage.class);
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/Pages.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/Pages.java
new file mode 100644
index 0000000000..b8d85ea08b
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/Pages.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.selenium.page;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.List;
+import org.openqa.selenium.By;
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+/**
+ * Helper class for selenium page objects.
+ *
+ * @author Sebastian Sdorra
+ */
+public final class Pages {
+
+ private Pages() {
+ }
+
+ /**
+ * Creates an instance of the given page object.
+ *
+ * @param page object type
+ * @param driver selenium driver
+ * @param clazz page object type
+ * @param otherArguments other constructor arguments
+ *
+ * @return instance of page object
+ */
+ public static T get(WebDriver driver, Class clazz, Object... otherArguments)
+ {
+ T page = null;
+ try {
+ List> argumentTypes = Lists.newArrayList();
+ argumentTypes.add(WebDriver.class);
+ for (Object argument : otherArguments) {
+ argumentTypes.add(argument.getClass());
+ }
+
+ List