Merge with 2.0.0-m3

sonia.scm.it.RepositoryHookITCase breaks for hg.
This commit is contained in:
René Pfeuffer
2018-08-31 10:40:12 +02:00
1810 changed files with 33506 additions and 207291 deletions

View File

@@ -34,13 +34,14 @@ package sonia.scm;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
//~--- JDK imports ------------------------------------------------------------
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
@@ -97,7 +98,7 @@ public class ClassOverride implements Validateable
public String toString()
{
//J-
return Objects.toStringHelper(this)
return MoreObjects.toStringHelper(this)
.add("bind", bind)
.add("to", to)
.toString();

View File

@@ -0,0 +1,24 @@
package sonia.scm;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* This dispatcher forwards every request to the index.html of the application.
*
* @since 2.0.0
*/
public class ForwardingPushStateDispatcher implements PushStateDispatcher {
@Override
public void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher("/index.html");
try {
dispatcher.forward(request, response);
} catch (ServletException e) {
throw new IOException("failed to forward request", e);
}
}
}

View File

@@ -7,19 +7,15 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class ManagerDaoAdapter<T extends ModelObject, E extends Exception> {
public class ManagerDaoAdapter<T extends ModelObject> {
private final GenericDAO<T> dao;
private final Function<T, E> notFoundException;
private final Function<T, E> alreadyExistsException;
public ManagerDaoAdapter(GenericDAO<T> dao, Function<T, E> notFoundException, Function<T, E> alreadyExistsException) {
public ManagerDaoAdapter(GenericDAO<T> dao) {
this.dao = dao;
this.notFoundException = notFoundException;
this.alreadyExistsException = alreadyExistsException;
}
public void modify(T object, Function<T, PermissionCheck> permissionCheck, AroundHandler<T, E> beforeUpdate, AroundHandler<T, E> afterUpdate) throws E {
public void modify(T object, Function<T, PermissionCheck> permissionCheck, AroundHandler<T> beforeUpdate, AroundHandler<T> afterUpdate) throws NotFoundException {
T notModified = dao.get(object.getId());
if (notModified != null) {
permissionCheck.apply(notModified).check();
@@ -34,19 +30,19 @@ public class ManagerDaoAdapter<T extends ModelObject, E extends Exception> {
afterUpdate.handle(notModified);
} else {
throw notFoundException.apply(object);
throw new NotFoundException();
}
}
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T, E> beforeCreate, AroundHandler<T, E> afterCreate) throws E {
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate) throws AlreadyExistsException {
return create(newObject, permissionCheck, beforeCreate, afterCreate, dao::contains);
}
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T, E> beforeCreate, AroundHandler<T, E> afterCreate, Predicate<T> existsCheck) throws E {
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate, Predicate<T> existsCheck) throws AlreadyExistsException {
permissionCheck.get().check();
AssertUtil.assertIsValid(newObject);
if (existsCheck.test(newObject)) {
throw alreadyExistsException.apply(newObject);
throw new AlreadyExistsException();
}
newObject.setCreationDate(System.currentTimeMillis());
beforeCreate.handle(newObject);
@@ -55,19 +51,19 @@ public class ManagerDaoAdapter<T extends ModelObject, E extends Exception> {
return newObject;
}
public void delete(T toDelete, Supplier<PermissionCheck> permissionCheck, AroundHandler<T, E> beforeDelete, AroundHandler<T, E> afterDelete) throws E {
public void delete(T toDelete, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeDelete, AroundHandler<T> afterDelete) throws NotFoundException {
permissionCheck.get().check();
if (dao.contains(toDelete)) {
beforeDelete.handle(toDelete);
dao.delete(toDelete);
afterDelete.handle(toDelete);
} else {
throw notFoundException.apply(toDelete);
throw new NotFoundException();
}
}
@FunctionalInterface
public interface AroundHandler<T extends ModelObject, E extends Exception> {
void handle(T notModified) throws E;
public interface AroundHandler<T extends ModelObject> {
void handle(T notModified);
}
}

View File

@@ -0,0 +1,132 @@
package sonia.scm;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.ByteStreams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
/**
* PushStateDispatcher which delegates the request to a different server. This dispatcher should only be used for
* development and never in production.
*
* @since 2.0.0
*/
public final class ProxyPushStateDispatcher implements PushStateDispatcher {
private static final Logger LOG = LoggerFactory.getLogger(ProxyPushStateDispatcher.class);
@FunctionalInterface
interface ConnectionFactory {
HttpURLConnection open(URL url) throws IOException;
}
private final String target;
private final ConnectionFactory connectionFactory;
/**
* Creates a new dispatcher for the given target. The target must be a valid url.
*
* @param target proxy target
*/
public ProxyPushStateDispatcher(String target) {
this(target, ProxyPushStateDispatcher::openConnection);
}
/**
* This Constructor should only be used for testing.
*
* @param target proxy target
* @param connectionFactory factory for creating an connection from a url
*/
@VisibleForTesting
ProxyPushStateDispatcher(String target, ConnectionFactory connectionFactory) {
this.target = target;
this.connectionFactory = connectionFactory;
}
@Override
public void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException {
URL url = createProxyUrl(uri);
HttpURLConnection connection = connectionFactory.open(url);
connection.setRequestMethod(request.getMethod());
copyRequestHeaders(request, connection);
if (request.getContentLength() > 0) {
copyRequestBody(request, connection);
}
int responseCode = connection.getResponseCode();
response.setStatus(responseCode);
copyResponseHeaders(response, connection);
if (connection.getContentLength() > 0) {
copyResponseBody(response, connection);
}
}
private void copyResponseBody(HttpServletResponse response, HttpURLConnection connection) throws IOException {
try (InputStream input = getConnectionInput(connection); OutputStream output = response.getOutputStream()) {
ByteStreams.copy(input, output);
}
}
private InputStream getConnectionInput(HttpURLConnection connection) throws IOException {
if (connection.getErrorStream() != null) {
return connection.getErrorStream();
}
return connection.getInputStream();
}
private void copyResponseHeaders(HttpServletResponse response, HttpURLConnection connection) {
Map<String, List<String>> headerFields = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : headerFields.entrySet()) {
if (entry.getKey() != null && !"Transfer-Encoding".equalsIgnoreCase(entry.getKey())) {
for (String value : entry.getValue()) {
response.addHeader(entry.getKey(), value);
}
}
}
}
private void copyRequestBody(HttpServletRequest request, HttpURLConnection connection) throws IOException {
connection.setDoOutput(true);
try (InputStream input = request.getInputStream(); OutputStream output = connection.getOutputStream()) {
ByteStreams.copy(input, output);
}
}
private void copyRequestHeaders(HttpServletRequest request, HttpURLConnection connection) {
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String header = headers.nextElement();
Enumeration<String> values = request.getHeaders(header);
while (values.hasMoreElements()) {
String value = values.nextElement();
connection.setRequestProperty(header, value);
}
}
}
private URL createProxyUrl(String uri) throws MalformedURLException {
return new URL(target + uri);
}
private static HttpURLConnection openConnection(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}

View File

@@ -0,0 +1,28 @@
package sonia.scm;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* The PushStateDispatcher is responsible for dispatching the request, to the main entry point of the ui, if no resource
* could be found for the requested path. This allows us the implementation of a ui which work with "pushstate" of
* html5.
*
* @since 2.0.0
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API">HTML5 Push State</a>
*/
public interface PushStateDispatcher {
/**
* Dispatches the request to the main entry point of the ui.
*
* @param request http request
* @param response http response
* @param uri request uri
*
* @throws IOException
*/
void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) throws IOException;
}

View File

@@ -0,0 +1,28 @@
package sonia.scm;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import javax.inject.Provider;
/**
* Injection Provider for the {@link PushStateDispatcher}. The provider will return a {@link ProxyPushStateDispatcher}
* if the system property {@code PushStateDispatcherProvider#PROPERTY_TARGET} is set to a proxy target url, otherwise
* a {@link ForwardingPushStateDispatcher} is used.
*
* @since 2.0.0
*/
public class PushStateDispatcherProvider implements Provider<PushStateDispatcher> {
@VisibleForTesting
static final String PROPERTY_TARGET = "sonia.scm.ui.proxy";
@Override
public PushStateDispatcher get() {
String target = System.getProperty(PROPERTY_TARGET);
if (Strings.isNullOrEmpty(target)) {
return new ForwardingPushStateDispatcher();
}
return new ProxyPushStateDispatcher(target);
}
}

View File

@@ -63,10 +63,6 @@ import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.resources.DefaultResourceManager;
import sonia.scm.resources.DevelopmentResourceManager;
import sonia.scm.resources.ResourceManager;
import sonia.scm.resources.ScriptResourceServlet;
import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler;
import sonia.scm.security.*;
@@ -266,16 +262,6 @@ public class ScmServletModule extends ServletModule
transformers.addBinding().to(JsonContentTransformer.class);
bind(AdvancedHttpClient.class).to(DefaultAdvancedHttpClient.class);
// bind resourcemanager
if (context.getStage() == Stage.DEVELOPMENT)
{
bind(ResourceManager.class, DevelopmentResourceManager.class);
}
else
{
bind(ResourceManager.class, DefaultResourceManager.class);
}
// bind repository service factory
bind(RepositoryServiceFactory.class);
@@ -295,9 +281,6 @@ public class ScmServletModule extends ServletModule
// debug servlet
serve(PATTERN_DEBUG).with(DebugServlet.class);
// plugin resources
serve(PATTERN_PLUGIN_SCRIPT).with(ScriptResourceServlet.class);
// template
serve(PATTERN_INDEX, "/").with(TemplateServlet.class);
@@ -313,7 +296,7 @@ public class ScmServletModule extends ServletModule
// bind events
// bind(LastModifiedUpdateListener.class);
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
}

View File

@@ -0,0 +1,85 @@
package sonia.scm;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.filter.WebElement;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.UberWebResourceLoader;
import sonia.scm.util.HttpUtil;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
/**
* WebResourceServlet serves resources from the {@link UberWebResourceLoader}.
*
* @since 2.0.0
*/
@Singleton
@WebElement(value = WebResourceServlet.PATTERN, regex = true)
public class WebResourceServlet extends HttpServlet {
/**
* exclude api requests and the old frontend servlets.
*
* TODO remove old protocol servlets
*/
@VisibleForTesting
static final String PATTERN = "/(?!api/|git/|hg/|svn/|debug/).*";
private static final Logger LOG = LoggerFactory.getLogger(WebResourceServlet.class);
private final UberWebResourceLoader webResourceLoader;
private final PushStateDispatcher pushStateDispatcher;
@Inject
public WebResourceServlet(PluginLoader pluginLoader, PushStateDispatcher dispatcher) {
this.webResourceLoader = pluginLoader.getUberWebResourceLoader();
this.pushStateDispatcher = dispatcher;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
String uri = normalizeUri(request);
LOG.trace("try to load {}", uri);
URL url = webResourceLoader.getResource(uri);
if (url != null) {
serveResource(response, url);
} else {
dispatch(request, response, uri);
}
}
private void dispatch(HttpServletRequest request, HttpServletResponse response, String uri) {
try {
pushStateDispatcher.dispatch(request, response, uri);
} catch (IOException ex) {
LOG.error("failed to dispatch: " + uri, ex);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
private String normalizeUri(HttpServletRequest request) {
return HttpUtil.getStrippedURI(request);
}
private void serveResource(HttpServletResponse response, URL url) {
// TODO lastModifiedDate, if-... ???
try (OutputStream output = response.getOutputStream()) {
Resources.copy(url, output);
} catch (IOException ex) {
LOG.warn("failed to serve resource: {}", url);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}

View File

@@ -1,6 +1,6 @@
package sonia.scm.api.rest;
import sonia.scm.user.UserAlreadyExistsException;
import sonia.scm.AlreadyExistsException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@@ -8,9 +8,9 @@ import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class UserAlreadyExistsExceptionMapper implements ExceptionMapper<UserAlreadyExistsException> {
public class AlreadyExistsExceptionMapper implements ExceptionMapper<AlreadyExistsException> {
@Override
public Response toResponse(UserAlreadyExistsException exception) {
public Response toResponse(AlreadyExistsException exception) {
return Response.status(Status.CONFLICT).build();
}
}

View File

@@ -0,0 +1,15 @@
package sonia.scm.api.rest;
import sonia.scm.ConcurrentModificationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class ConcurrentModificationExceptionMapper implements ExceptionMapper<ConcurrentModificationException> {
@Override
public Response toResponse(ConcurrentModificationException exception) {
return Response.status(Response.Status.CONFLICT).build();
}
}

View File

@@ -1,16 +0,0 @@
package sonia.scm.api.rest;
import sonia.scm.group.GroupAlreadyExistsException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class GroupAlreadyExistsExceptionMapper implements ExceptionMapper<GroupAlreadyExistsException> {
@Override
public Response toResponse(GroupAlreadyExistsException exception) {
return Response.status(Status.CONFLICT).build();
}
}

View File

@@ -34,15 +34,15 @@ package sonia.scm.api.rest;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
//~--- JDK imports ------------------------------------------------------------
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -127,7 +127,7 @@ public class Permission implements Serializable
public String toString()
{
//J-
return Objects.toStringHelper(this)
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("value", value)
.toString();

View File

@@ -1,16 +0,0 @@
package sonia.scm.api.rest;
import sonia.scm.repository.RepositoryAlreadyExistsException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class RepositoryAlreadyExistsExceptionMapper implements ExceptionMapper<RepositoryAlreadyExistsException> {
@Override
public Response toResponse(RepositoryAlreadyExistsException exception) {
return Response.status(Status.CONFLICT).build();
}
}

View File

@@ -34,15 +34,16 @@ package sonia.scm.api.rest;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Throwables;
//~--- JDK imports ------------------------------------------------------------
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
@@ -145,7 +146,7 @@ public class RestExceptionResult
public String toString()
{
//J-
return Objects.toStringHelper(this)
return MoreObjects.toStringHelper(this)
.add("message", message)
.add("stacktrace", stacktrace)
.toString();

View File

@@ -36,11 +36,11 @@ package sonia.scm.api.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra

View File

@@ -48,8 +48,13 @@ import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import javax.ws.rs.core.*;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
@@ -61,28 +66,19 @@ import java.util.Date;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*
* @param <T>
* @param <E>
*/
public abstract class AbstractManagerResource<T extends ModelObject,
E extends Exception>
{
public abstract class AbstractManagerResource<T extends ModelObject> {
/** the logger for AbstractManagerResource */
private static final Logger logger =
LoggerFactory.getLogger(AbstractManagerResource.class);
protected final Manager<T, E> manager;
protected final Manager<T> manager;
private final Class<T> type;
protected int cacheMaxAge = 0;
protected boolean disableCache = false;
public AbstractManagerResource(Manager<T, E> manager, Class<T> type) {
public AbstractManagerResource(Manager<T> manager, Class<T> type) {
this.manager = manager;
this.type = type;
}

View File

@@ -1,44 +1,8 @@
/**
* 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.api.rest.resources;
//~--- non-JDK imports --------------------------------------------------------
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.PathNotFoundException;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.CatCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
@@ -50,97 +14,40 @@ import javax.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.io.OutputStream;
/**
*
* @author Sebastian Sdorra
*/
public class BrowserStreamingOutput implements StreamingOutput
{
public class BrowserStreamingOutput implements StreamingOutput {
/** the logger for BrowserStreamingOutput */
private static final Logger logger =
LoggerFactory.getLogger(BrowserStreamingOutput.class);
//~--- constructors ---------------------------------------------------------
private final CatCommandBuilder builder;
private final String path;
private final RepositoryService repositoryService;
/**
* Constructs ...
*
*
* @param browser
* @param revision
*
* @param repositoryService
*
* @param builder
* @param path
*/
public BrowserStreamingOutput(RepositoryService repositoryService,
CatCommandBuilder builder, String path)
{
CatCommandBuilder builder, String path) {
this.repositoryService = repositoryService;
this.builder = builder;
this.path = path;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param output
*
* @throws IOException
* @throws WebApplicationException
*/
@Override
public void write(OutputStream output)
throws IOException, WebApplicationException
{
try
{
public void write(OutputStream output) throws IOException {
try {
builder.retriveContent(output, path);
}
catch (PathNotFoundException ex)
{
if (logger.isWarnEnabled())
{
} catch (PathNotFoundException ex) {
if (logger.isWarnEnabled()) {
logger.warn("could not find path {}", ex.getPath());
}
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
catch (RevisionNotFoundException ex)
{
if (logger.isWarnEnabled())
{
} catch (RevisionNotFoundException ex) {
if (logger.isWarnEnabled()) {
logger.warn("could not find revision {}", ex.getRevision());
}
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
catch (RepositoryException ex)
{
logger.error("could not write content to page", ex);
throw new WebApplicationException(ex,
Response.Status.INTERNAL_SERVER_ERROR);
}
finally
{
} finally {
IOUtil.close(repositoryService);
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final CatCommandBuilder builder;
/** Field description */
private final String path;
/** Field description */
private final RepositoryService repositoryService;
}

View File

@@ -39,26 +39,20 @@ import com.google.inject.Inject;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ConcurrentModificationException;
import sonia.scm.NotFoundException;
import sonia.scm.api.rest.RestActionResult;
import sonia.scm.security.Role;
import sonia.scm.security.ScmSecurityException;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.util.AssertUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -67,6 +61,8 @@ import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
//~--- JDK imports ------------------------------------------------------------
/**
* Resource to change the password of the authenticated user.
*
@@ -104,11 +100,6 @@ public class ChangePasswordResource
*
* @param oldPassword old password of the current user
* @param newPassword new password for the current user
*
* @return
*
* @throws IOException
* @throws UserException
*/
@POST
@TypeHint(RestActionResult.class)
@@ -118,10 +109,7 @@ public class ChangePasswordResource
@ResponseCode(code = 500, condition = "internal server error")
})
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response changePassword(@FormParam("old-password") String oldPassword,
@FormParam("new-password") String newPassword)
throws UserException, IOException
{
public Response changePassword(@FormParam("old-password") String oldPassword, @FormParam("new-password") String newPassword) throws NotFoundException, ConcurrentModificationException {
AssertUtil.assertIsNotEmpty(oldPassword);
AssertUtil.assertIsNotEmpty(newPassword);

View File

@@ -35,26 +35,20 @@ package sonia.scm.api.rest.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.Closeables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.PathNotFoundException;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
import sonia.scm.util.IOUtil;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import sonia.scm.util.IOUtil;
import java.io.IOException;
import java.io.OutputStream;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -96,22 +90,11 @@ public class DiffStreamingOutput implements StreamingOutput
* @throws WebApplicationException
*/
@Override
public void write(OutputStream output)
throws IOException, WebApplicationException
{
public void write(OutputStream output) throws IOException {
try
{
builder.retriveContent(output);
}
catch (PathNotFoundException ex)
{
if (logger.isWarnEnabled())
{
logger.warn("could not find path {}", ex.getPath());
}
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
catch (RevisionNotFoundException ex)
{
if (logger.isWarnEnabled())
@@ -121,13 +104,13 @@ public class DiffStreamingOutput implements StreamingOutput
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
catch (RepositoryException ex)
{
logger.error("could not write content to page", ex);
throw new WebApplicationException(ex,
Response.Status.INTERNAL_SERVER_ERROR);
}
// catch (RepositoryException ex)
// {
// logger.error("could not write content to page", ex);
//
// throw new WebApplicationException(ex,
// Response.Status.INTERNAL_SERVER_ERROR);
// }
finally
{
IOUtil.close(repositoryService);

View File

@@ -43,12 +43,25 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import sonia.scm.group.Group;
import sonia.scm.group.GroupException;
import sonia.scm.group.GroupManager;
import sonia.scm.security.Role;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.Collection;
//~--- JDK imports ------------------------------------------------------------
@@ -60,23 +73,13 @@ import java.util.Collection;
*/
@Path("groups")
@Singleton
public class GroupResource
extends AbstractManagerResource<Group, GroupException>
{
public class GroupResource extends AbstractManagerResource<Group> {
/** Field description */
public static final String PATH_PART = "groups";
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
*
* @param securityContextProvider
* @param groupManager
*/
@Inject
public GroupResource(GroupManager groupManager)
{

View File

@@ -33,9 +33,7 @@
package sonia.scm.api.rest.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
@@ -47,6 +45,8 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.AlreadyExistsException;
import sonia.scm.NotFoundException;
import sonia.scm.NotSupportedFeatuerException;
import sonia.scm.Type;
import sonia.scm.api.rest.RestActionUploadResult;
@@ -75,8 +75,6 @@ import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
//~--- JDK imports ------------------------------------------------------------
/**
* Rest resource for importing repositories.
*
@@ -256,10 +254,6 @@ public class RepositoryImportResource
service = serviceFactory.create(repository);
service.getPullCommand().pull(request.getUrl());
}
catch (RepositoryException ex)
{
handleImportFailure(ex, repository);
}
catch (IOException ex)
{
handleImportFailure(ex, repository);
@@ -413,11 +407,6 @@ public class RepositoryImportResource
logger.warn("exception occured durring directory import", ex);
response = Response.serverError().build();
}
catch (RepositoryException ex)
{
logger.warn("exception occured durring directory import", ex);
response = Response.serverError().build();
}
}
else
{
@@ -526,14 +515,14 @@ public class RepositoryImportResource
// repository = new Repository(null, type, name);
manager.create(repository);
}
catch (RepositoryAlreadyExistsException ex)
catch (AlreadyExistsException ex)
{
logger.warn("a {} repository with the name {} already exists", type,
name);
throw new WebApplicationException(Response.Status.CONFLICT);
}
catch (RepositoryException ex)
catch (InternalRepositoryException ex)
{
handleGenericCreationFailure(ex, type, name);
}
@@ -583,11 +572,7 @@ public class RepositoryImportResource
service = serviceFactory.create(repository);
service.getUnbundleCommand().setCompressed(compressed).unbundle(file);
}
catch (RepositoryException ex)
{
handleImportFailure(ex, repository);
}
catch (IOException ex)
catch (InternalRepositoryException ex)
{
handleImportFailure(ex, repository);
}
@@ -685,9 +670,9 @@ public class RepositoryImportResource
{
manager.delete(repository);
}
catch (RepositoryException e)
catch (InternalRepositoryException | NotFoundException e)
{
logger.error("can not delete repository", e);
logger.error("can not delete repository after import failure", e);
}
throw new WebApplicationException(ex,
@@ -741,7 +726,7 @@ public class RepositoryImportResource
{
throw new WebApplicationException(ex);
}
catch (RepositoryException ex)
catch (InternalRepositoryException ex)
{
throw new WebApplicationException(ex);
}
@@ -812,7 +797,7 @@ public class RepositoryImportResource
public String toString()
{
//J-
return Objects.toStringHelper(this)
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("url", url)
.toString();

View File

@@ -46,16 +46,51 @@ import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.*;
import sonia.scm.repository.api.*;
import sonia.scm.NotFoundException;
import sonia.scm.repository.BlameResult;
import sonia.scm.repository.Branches;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.HealthChecker;
import sonia.scm.repository.Permission;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryIsNotArchivedException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.Tags;
import sonia.scm.repository.api.BlameCommandBuilder;
import sonia.scm.repository.api.BrowseCommandBuilder;
import sonia.scm.repository.api.CatCommandBuilder;
import sonia.scm.repository.api.CommandNotSupportedException;
import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.LogCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -69,7 +104,7 @@ import java.util.Collection;
*/
@Singleton
@Path("repositories")
public class RepositoryResource extends AbstractManagerResource<Repository, RepositoryException>
public class RepositoryResource extends AbstractManagerResource<Repository>
{
/** Field description */
@@ -169,12 +204,15 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
{
logger.warn("delete not allowed", ex);
response = Response.status(Response.Status.FORBIDDEN).build();
} catch (NotFoundException e) {
// there is nothing to do because delete should be idempotent
response = Response.ok().build();
}
catch (RepositoryException ex)
{
logger.error("error during delete", ex);
response = createErrorResponse(ex);
}
// catch (IOException ex)
// {
// logger.error("error during delete", ex);
// response = Response.serverError().build();
// }
}
else
{
@@ -215,10 +253,8 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
{
logger.warn("could not find repository ".concat(id), ex);
response = Response.status(Status.NOT_FOUND).build();
}
catch (RepositoryException | IOException ex)
{
logger.error("error occured during health check", ex);
} catch (NotFoundException e) {
logger.error("error occured during health check", e);
response = Response.serverError().build();
}
@@ -312,7 +348,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
* @return a annotate/blame view for the given path
*
* @throws IOException
* @throws RepositoryException
*/
@GET
@Path("{id}/blame")
@@ -326,7 +361,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getBlame(@PathParam("id") String id,
@QueryParam("revision") String revision, @QueryParam("path") String path)
throws RepositoryException, IOException
throws IOException
{
Response response = null;
RepositoryService service = null;
@@ -382,7 +417,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
* @return all {@link Branches} of a repository
*
* @throws IOException
* @throws RepositoryException
*
* @since 1.18
*/
@@ -397,7 +431,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
@TypeHint(Branches.class)
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getBranches(@PathParam("id") String id)
throws RepositoryException, IOException
throws IOException
{
Response response = null;
RepositoryService service = null;
@@ -446,7 +480,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
* @return a list of folders and files for the given folder
*
* @throws IOException
* @throws RepositoryException
*/
@GET
@Path("{id}/browse")
@@ -466,7 +499,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
@QueryParam("disableLastCommit") @DefaultValue("false") boolean disableLastCommit,
@QueryParam("disableSubRepositoryDetection") @DefaultValue("false") boolean disableSubRepositoryDetection,
@QueryParam("recursive") @DefaultValue("false") boolean recursive)
throws RepositoryException, IOException
throws IOException
//J+
{
Response response = null;
@@ -505,7 +538,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
response = Response.status(Response.Status.NOT_FOUND).build();
}
}
catch (RepositoryNotFoundException ex)
catch (NotFoundException ex)
{
response = Response.status(Response.Status.NOT_FOUND).build();
}
@@ -531,7 +564,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
* @return a {@link Changeset} from the given repository
*
* @throws IOException
* @throws RepositoryException
*/
@GET
@Path("{id}/changeset/{revision}")
@@ -545,7 +577,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getChangeset(@PathParam("id") String id,
@PathParam("revision") String revision)
throws IOException, RepositoryException
throws IOException
{
Response response = null;
@@ -568,7 +600,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
response = Response.status(Status.NOT_FOUND).build();
}
}
catch (RepositoryNotFoundException ex)
catch (NotFoundException ex)
{
response = Response.status(Response.Status.NOT_FOUND).build();
}
@@ -603,7 +635,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
* @return a list of {@link Changeset} for the given repository
*
* @throws IOException
* @throws RepositoryException
*/
@GET
@Path("{id}/changesets")
@@ -623,7 +654,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
@QueryParam("branch") String branch,
@DefaultValue("0") @QueryParam("start") int start,
@DefaultValue("20") @QueryParam("limit") int limit
) throws RepositoryException, IOException
) throws IOException
//J+
{
Response response = null;
@@ -664,7 +695,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
response = Response.ok().build();
}
}
catch (RepositoryNotFoundException ex)
catch (NotFoundException ex)
{
response = Response.status(Response.Status.NOT_FOUND).build();
}
@@ -759,7 +790,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
* @return the modifications of a {@link Changeset}
*
* @throws IOException
* @throws RepositoryException
*/
@GET
@Path("{id}/diff")
@@ -774,7 +804,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
public Response getDiff(@PathParam("id") String id,
@QueryParam("revision") String revision, @QueryParam("path") String path,
@QueryParam("format") DiffFormat format)
throws RepositoryException, IOException
throws IOException
{
AssertUtil.assertIsNotEmpty(id);
AssertUtil.assertIsNotEmpty(revision);
@@ -841,7 +871,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
* @return all {@link Tags} of a repository
*
* @throws IOException
* @throws RepositoryException
*
* @since 1.18
*/
@@ -856,7 +885,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
@TypeHint(Tags.class)
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getTags(@PathParam("id") String id)
throws RepositoryException, IOException
throws IOException
{
Response response = null;
RepositoryService service = null;

View File

@@ -45,13 +45,25 @@ import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.PasswordService;
import sonia.scm.security.Role;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.Util;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.Collection;
//~--- JDK imports ------------------------------------------------------------
@@ -63,7 +75,7 @@ import java.util.Collection;
*/
@Singleton
@Path("users")
public class UserResource extends AbstractManagerResource<User, UserException>
public class UserResource extends AbstractManagerResource<User>
{
/** Field description */

View File

@@ -0,0 +1,58 @@
package sonia.scm.api.v2;
import lombok.Getter;
import org.jboss.resteasy.api.validation.ResteasyViolationException;
import javax.validation.ConstraintViolation;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
import java.util.stream.Collectors;
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ResteasyViolationException> {
@Override
public Response toResponse(ResteasyViolationException exception) {
List<ConstraintViolationBean> violations =
exception.getConstraintViolations()
.stream()
.map(ConstraintViolationBean::new)
.collect(Collectors.toList());
return Response
.status(Response.Status.BAD_REQUEST)
.type(MediaType.APPLICATION_JSON_TYPE)
.entity(new ValidationError(violations))
.build();
}
@Getter
public static class ValidationError {
@XmlElement(name = "violation")
@XmlElementWrapper(name = "violations")
private List<ConstraintViolationBean> violations;
public ValidationError(List<ConstraintViolationBean> violations) {
this.violations = violations;
}
}
@XmlRootElement(name = "violation")
@Getter
public static class ConstraintViolationBean {
private String path;
private String message;
public ConstraintViolationBean(ConstraintViolation<?> violation) {
message = violation.getMessage();
path = violation.getPropertyPath().toString();
}
}
}

View File

@@ -9,7 +9,6 @@ import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.api.CommandNotSupportedException;
@@ -65,7 +64,7 @@ public class BranchRootResource {
@ResponseCode(code = 404, condition = "not found, no branch with the specified name for the repository available or repository not found"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("branch") String branchName) throws IOException, RepositoryException {
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("branch") String branchName) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Branches branches = repositoryService.getBranchesCommand().getBranches();
return branches.getBranches()
@@ -136,7 +135,7 @@ public class BranchRootResource {
@ResponseCode(code = 404, condition = "not found, no repository found for the given namespace and name"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException, RepositoryException {
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Branches branches = repositoryService.getBranchesCommand().getBranches();
return Response.ok(branchCollectionToDtoMapper.map(namespace, name, branches.getBranches())).build();

View File

@@ -29,7 +29,7 @@ public abstract class BranchToBranchDtoMapper {
.self(resourceLinks.branch().self(namespaceAndName, target.getName()))
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build())
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build())
.single(linkBuilder("source", resourceLinks.source().source(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build());
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build());
target.add(linksBuilder.build());
}
}

View File

@@ -0,0 +1,40 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Iterator;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
public class BrowserResultDto extends HalRepresentation implements Iterable<FileObjectDto> {
private String revision;
private String tag;
private String branch;
// REVIEW files nicht embedded?
private List<FileObjectDto> files;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
// REVIEW return null?
@Override
public Iterator<FileObjectDto> iterator() {
Iterator<FileObjectDto> it = null;
if (files != null)
{
it = files.iterator();
}
return it;
}
}

View File

@@ -0,0 +1,50 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
public class BrowserResultToBrowserResultDtoMapper {
@Inject
private FileObjectToFileObjectDtoMapper fileObjectToFileObjectDtoMapper;
@Inject
private ResourceLinks resourceLinks;
public BrowserResultDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName) {
BrowserResultDto browserResultDto = new BrowserResultDto();
browserResultDto.setTag(browserResult.getTag());
browserResultDto.setBranch(browserResult.getBranch());
browserResultDto.setRevision(browserResult.getRevision());
List<FileObjectDto> fileObjectDtoList = new ArrayList<>();
for (FileObject fileObject : browserResult.getFiles()) {
fileObjectDtoList.add(mapFileObject(fileObject, namespaceAndName, browserResult.getRevision()));
}
browserResultDto.setFiles(fileObjectDtoList);
this.addLinks(browserResult, browserResultDto, namespaceAndName);
return browserResultDto;
}
private FileObjectDto mapFileObject(FileObject fileObject, NamespaceAndName namespaceAndName, String revision) {
return fileObjectToFileObjectDtoMapper.map(fileObject, namespaceAndName, revision);
}
private void addLinks(BrowserResult browserResult, BrowserResultDto dto, NamespaceAndName namespaceAndName) {
if (browserResult.getRevision() == null) {
dto.add(Links.linkingTo().self(resourceLinks.source().selfWithoutRevision(namespaceAndName.getNamespace(), namespaceAndName.getName())).build());
} else {
dto.add(Links.linkingTo().self(resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision())).build());
}
}
}

View File

@@ -9,8 +9,9 @@ import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.VndMediaType;
@@ -54,7 +55,7 @@ public class ChangesetRootResource {
@Produces(VndMediaType.CHANGESET_COLLECTION)
@TypeHint(CollectionDto.class)
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RepositoryException {
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RevisionNotFoundException, RepositoryNotFoundException {
RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name));
Repository repository = repositoryService.getRepository();
RepositoryPermissions.read(repository).check();
@@ -81,7 +82,7 @@ public class ChangesetRootResource {
@Produces(VndMediaType.CHANGESET)
@TypeHint(ChangesetDto.class)
@Path("{id}")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws RepositoryException, IOException {
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException, RevisionNotFoundException, RepositoryNotFoundException {
RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name));
Repository repository = repositoryService.getRepository();
RepositoryPermissions.read(repository).check();

View File

@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import sonia.scm.AlreadyExistsException;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.PageResult;
@@ -22,16 +23,14 @@ import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
*
* @param <MODEL_OBJECT> The type of the model object, eg. {@link sonia.scm.user.User}.
* @param <DTO> The corresponding transport object, eg. {@link UserDto}.
* @param <EXCEPTION> The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
*
* @see SingleResourceManagerAdapter
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> extends AbstractManagerResource<MODEL_OBJECT, EXCEPTION> {
DTO extends HalRepresentation> extends AbstractManagerResource<MODEL_OBJECT> {
CollectionResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager, Class<MODEL_OBJECT> type) {
CollectionResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
super(manager, type);
}
@@ -48,7 +47,7 @@ class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
* Creates a model object for the given dto and returns a corresponding http response.
* This handles all corner cases, eg. no conflicts or missing privileges.
*/
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws EXCEPTION {
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws AlreadyExistsException {
if (dto == null) {
return Response.status(BAD_REQUEST).build();
}

View File

@@ -8,7 +8,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.PathNotFoundException;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.RepositoryService;
@@ -61,8 +60,8 @@ public class ContentResource {
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
StreamingOutput stream = createStreamingOutput(namespace, name, revision, path);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
StreamingOutput stream = createStreamingOutput(namespace, name, revision, path, repositoryService);
Response.ResponseBuilder responseBuilder = Response.ok(stream);
return createContentHeader(namespace, name, revision, path, repositoryService, responseBuilder);
} catch (RepositoryNotFoundException e) {
@@ -71,17 +70,20 @@ public class ContentResource {
}
}
private StreamingOutput createStreamingOutput(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path, RepositoryService repositoryService) {
private StreamingOutput createStreamingOutput(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
return os -> {
try {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
repositoryService.getCatCommand().setRevision(revision).retriveContent(os, path);
os.close();
} catch (RepositoryNotFoundException e) {
LOG.debug("repository {}/{} not found", path, namespace, name, e);
throw new WebApplicationException(Status.NOT_FOUND);
} catch (PathNotFoundException e) {
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
throw new WebApplicationException(Status.NOT_FOUND);
} catch (RepositoryException e) {
LOG.info("error reading repository resource {} from {}/{}", path, namespace, name, e);
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
} catch (RevisionNotFoundException e) {
LOG.debug("revision '{}' not found in repository {}/{}", revision, namespace, name, e);
throw new WebApplicationException(Status.NOT_FOUND);
}
};
}
@@ -124,7 +126,7 @@ public class ContentResource {
} catch (RevisionNotFoundException e) {
LOG.debug("revision '{}' not found in repository {}/{}", revision, namespace, name, e);
return Response.status(Status.NOT_FOUND).build();
} catch (IOException | RepositoryException e) {
} catch (IOException e) {
LOG.info("error reading repository resource {} from {}/{}", path, namespace, name, e);
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
}
@@ -137,12 +139,14 @@ public class ContentResource {
contentType.getLanguage().ifPresent(language -> responseBuilder.header("Language", language));
}
private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException, RepositoryException {
private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException, PathNotFoundException, RevisionNotFoundException {
InputStream stream = repositoryService.getCatCommand().setRevision(revision).getStream(path);
try {
byte[] buffer = new byte[HEAD_BUFFER_SIZE];
int length = stream.read(buffer);
if (length < buffer.length) {
if (length < 0) { // empty file
return new byte[]{};
} else if (length < buffer.length) {
return Arrays.copyOf(buffer, length);
} else {
return buffer;

View File

@@ -0,0 +1,28 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
@NoArgsConstructor
public class FileObjectDto extends HalRepresentation {
private String name;
private String path;
private boolean directory;
private String description;
private int length;
private Instant lastModified;
private SubRepositoryDto subRepository;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,46 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.SubRepository;
import javax.inject.Inject;
import java.net.URI;
@Mapper
public abstract class FileObjectToFileObjectDtoMapper extends BaseMapper<FileObject, FileObjectDto> {
@Inject
private ResourceLinks resourceLinks;
protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context String revision);
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);
@AfterMapping
void addLinks(FileObject fileObject, @MappingTarget FileObjectDto dto, @Context NamespaceAndName namespaceAndName, @Context String revision) {
String path = removeFirstSlash(fileObject.getPath());
Links.Builder links = Links.linkingTo();
if (dto.isDirectory()) {
links.self(addPath(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, ""), path));
} else {
links.self(addPath(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, ""), path));
}
dto.add(links.build());
}
// we have to add the file path using URI, so that path separators (aka '/') will not be encoded as '%2F'
private String addPath(String sourceWithPath, String path) {
return URI.create(sourceWithPath).resolve(path).toASCIIString();
}
private String removeFirstSlash(String source) {
return source.startsWith("/") ? source.substring(1) : source;
}
}

View File

@@ -1,15 +1,25 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.*;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.AlreadyExistsException;
import sonia.scm.group.Group;
import sonia.scm.group.GroupException;
import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.io.IOException;
public class GroupCollectionResource {
@@ -19,7 +29,7 @@ public class GroupCollectionResource {
private final GroupCollectionToDtoMapper groupCollectionToDtoMapper;
private final ResourceLinks resourceLinks;
private final IdResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
private final IdResourceManagerAdapter<Group, GroupDto> adapter;
@Inject
public GroupCollectionResource(GroupManager manager, GroupDtoToGroupMapper dtoToGroupMapper, GroupCollectionToDtoMapper groupCollectionToDtoMapper, ResourceLinks resourceLinks) {
@@ -76,7 +86,7 @@ public class GroupCollectionResource {
})
@TypeHint(TypeHint.NO_CONTENT.class)
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created group"))
public Response create(GroupDto groupDto) throws IOException, GroupException {
public Response create(@Valid GroupDto groupDto) throws AlreadyExistsException {
return adapter.create(groupDto,
() -> dtoToGroupMapper.map(groupDto),
group -> resourceLinks.group().self(group.getName()));

View File

@@ -6,7 +6,9 @@ import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -18,7 +20,9 @@ public class GroupDto extends HalRepresentation {
private String description;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
@Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$")
private String name;
@NotEmpty
private String type;
private Map<String, String> properties;
private List<String> members;

View File

@@ -4,12 +4,12 @@ import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import sonia.scm.group.Group;
import java.time.Instant;
@Mapper
public abstract class GroupDtoToGroupMapper {
public abstract class GroupDtoToGroupMapper extends BaseDtoMapper {
@Mapping(target = "creationDate", ignore = true)
@Mapping(target = "lastModified", ignore = true)
public abstract Group map(GroupDto groupDto);
}

View File

@@ -3,20 +3,28 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.ConcurrentModificationException;
import sonia.scm.NotFoundException;
import sonia.scm.group.Group;
import sonia.scm.group.GroupException;
import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
public class GroupResource {
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
private final GroupDtoToGroupMapper dtoToGroupMapper;
private final IdResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
private final IdResourceManagerAdapter<Group, GroupDto> adapter;
@Inject
public GroupResource(GroupManager manager, GroupToGroupDtoMapper groupToGroupDtoMapper,
@@ -45,7 +53,7 @@ public class GroupResource {
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("id") String id) {
public Response get(@PathParam("id") String id) throws NotFoundException {
return adapter.get(id, groupToGroupDtoMapper::map);
}
@@ -90,7 +98,7 @@ public class GroupResource {
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response update(@PathParam("id") String name, GroupDto groupDto) {
public Response update(@PathParam("id") String name, @Valid GroupDto groupDto) throws NotFoundException, ConcurrentModificationException {
return adapter.update(name, existing -> dtoToGroupMapper.map(groupDto));
}
}

View File

@@ -1,12 +1,14 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import sonia.scm.AlreadyExistsException;
import sonia.scm.ConcurrentModificationException;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.NotFoundException;
import sonia.scm.PageResult;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -18,25 +20,24 @@ import java.util.function.Supplier;
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> {
DTO extends HalRepresentation> {
private final Manager<MODEL_OBJECT, EXCEPTION> manager;
private final Manager<MODEL_OBJECT> manager;
private final SingleResourceManagerAdapter<MODEL_OBJECT, DTO, EXCEPTION> singleAdapter;
private final CollectionResourceManagerAdapter<MODEL_OBJECT, DTO, EXCEPTION> collectionAdapter;
private final SingleResourceManagerAdapter<MODEL_OBJECT, DTO> singleAdapter;
private final CollectionResourceManagerAdapter<MODEL_OBJECT, DTO> collectionAdapter;
IdResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager, Class<MODEL_OBJECT> type) {
IdResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
this.manager = manager;
singleAdapter = new SingleResourceManagerAdapter<>(manager, type);
collectionAdapter = new CollectionResourceManagerAdapter<>(manager, type);
}
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) {
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) throws NotFoundException {
return singleAdapter.get(loadBy(id), mapToDto);
}
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) {
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) throws NotFoundException, ConcurrentModificationException {
return singleAdapter.update(
loadBy(id),
applyChanges,
@@ -48,7 +49,7 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
return collectionAdapter.getAll(page, pageSize, sortBy, desc, mapToDto);
}
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws IOException, EXCEPTION {
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws AlreadyExistsException {
return collectionAdapter.create(dto, modelObjectSupplier, uriCreator);
}

View File

@@ -33,6 +33,12 @@ public class MapperModule extends AbstractModule {
bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass());
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
// no mapstruct required
bind(UIPluginDtoMapper.class);
bind(UIPluginDtoCollectionMapper.class);
bind(UriInfoStore.class).in(ServletScopes.REQUEST);
}
}

View File

@@ -4,8 +4,8 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import sonia.scm.NotFoundException;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
@@ -28,7 +28,7 @@ public class MeResource {
private final UserToUserDtoMapper userToDtoMapper;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
private final IdResourceManagerAdapter<User, UserDto> adapter;
@Inject
public MeResource(UserToUserDtoMapper userToDtoMapper, UserManager manager) {
this.userToDtoMapper = userToDtoMapper;
@@ -47,7 +47,7 @@ public class MeResource {
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@Context Request request, @Context UriInfo uriInfo) {
public Response get(@Context Request request, @Context UriInfo uriInfo) throws NotFoundException {
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
return adapter.get(id, userToDtoMapper::map);

View File

@@ -31,8 +31,8 @@
package sonia.scm.api.v2.resources;
import sonia.scm.NotFoundException;
import sonia.scm.api.rest.StatusExceptionMapper;
import sonia.scm.repository.PermissionNotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
@@ -41,9 +41,9 @@ import javax.ws.rs.ext.Provider;
* @since 2.0.0
*/
@Provider
public class PermissionNotFoundExceptionMapper extends StatusExceptionMapper<PermissionNotFoundException> {
public class NotFoundExceptionMapper extends StatusExceptionMapper<NotFoundException> {
public PermissionNotFoundExceptionMapper() {
super(PermissionNotFoundException.class, Response.Status.NOT_FOUND);
public NotFoundExceptionMapper() {
super(NotFoundException.class, Response.Status.NOT_FOUND);
}
}

View File

@@ -1,49 +0,0 @@
/*
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.api.v2.resources;
import sonia.scm.api.rest.StatusExceptionMapper;
import sonia.scm.repository.PermissionAlreadyExistsException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
/**
* @since 2.0.0
*/
@Provider
public class PermissionAlreadyExistsExceptionMapper extends StatusExceptionMapper<PermissionAlreadyExistsException> {
public PermissionAlreadyExistsExceptionMapper() {
super(PermissionAlreadyExistsException.class, Response.Status.CONFLICT);
}
}

View File

@@ -6,11 +6,25 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import sonia.scm.repository.*;
import sonia.scm.AlreadyExistsException;
import sonia.scm.NotFoundException;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Permission;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Optional;
@@ -53,7 +67,7 @@ public class PermissionRootResource {
@TypeHint(TypeHint.NO_CONTENT.class)
@Consumes(VndMediaType.PERMISSION)
@Path("")
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name, PermissionDto permission) throws RepositoryException {
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name, PermissionDto permission) throws Exception {
log.info("try to add new permission: {}", permission);
Repository repository = load(namespace, name);
RepositoryPermissions.permissionWrite(repository).check();
@@ -70,7 +84,7 @@ public class PermissionRootResource {
* @param namespace the repository namespace
* @param name the repository name
* @return the http response with a list of permissionDto objects
* @throws RepositoryNotFoundException if the repository does not exists
* @throws NotFoundException if the repository does not exists
*/
@GET
@StatusCodes({
@@ -81,7 +95,7 @@ public class PermissionRootResource {
@Produces(VndMediaType.PERMISSION)
@TypeHint(PermissionDto.class)
@Path("{permission-name}")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) throws RepositoryException {
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) throws NotFoundException {
Repository repository = load(namespace, name);
RepositoryPermissions.permissionRead(repository).check();
return Response.ok(
@@ -90,7 +104,7 @@ public class PermissionRootResource {
.filter(permission -> permissionName.equals(permission.getName()))
.map(permission -> modelToDtoMapper.map(permission, repository))
.findFirst()
.orElseThrow(() -> new PermissionNotFoundException(repository, permissionName))
.orElseThrow(NotFoundException::new)
).build();
}
@@ -101,7 +115,7 @@ public class PermissionRootResource {
* @param namespace the repository namespace
* @param name the repository name
* @return the http response with a list of permissionDto objects
* @throws RepositoryNotFoundException if the repository does not exists
* @throws NotFoundException if the repository does not exists
*/
@GET
@StatusCodes({
@@ -138,7 +152,7 @@ public class PermissionRootResource {
public Response update(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("permission-name") String permissionName,
PermissionDto permission) throws RepositoryException {
PermissionDto permission) throws NotFoundException {
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
Repository repository = load(namespace, name);
RepositoryPermissions.permissionWrite(repository).check();
@@ -146,7 +160,7 @@ public class PermissionRootResource {
.stream()
.filter(perm -> StringUtils.isNotBlank(perm.getName()) && perm.getName().equals(permissionName))
.findFirst()
.orElseThrow(() -> new PermissionNotFoundException(repository, permissionName));
.orElseThrow(() -> new NotFoundException());
dtoToModelMapper.modify(existingPermission, permission);
manager.modify(repository);
log.info("the permission with name: {} is updated.", permissionName);
@@ -170,9 +184,9 @@ public class PermissionRootResource {
@Path("{permission-name}")
public Response delete(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("permission-name") String permissionName) throws RepositoryException {
@PathParam("permission-name") String permissionName) throws NotFoundException {
log.info("try to delete the permission with name: {}.", permissionName);
Repository repository = load(namespace, name);
Repository repository = load(namespace, name);
RepositoryPermissions.modify(repository).check();
repository.getPermissions()
.stream()
@@ -205,14 +219,14 @@ public class PermissionRootResource {
*
* @param permission the searched permission
* @param repository the repository to be inspected
* @throws PermissionAlreadyExistsException if the permission already exists in the repository
* @throws AlreadyExistsException if the permission already exists in the repository
*/
private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository) throws PermissionAlreadyExistsException {
private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository) throws AlreadyExistsException {
boolean isPermissionAlreadyExist = repository.getPermissions()
.stream()
.anyMatch(p -> p.getName().equals(permission.getName()));
if (isPermissionAlreadyExist) {
throw new PermissionAlreadyExistsException(repository, permission.getName());
throw new AlreadyExistsException();
}
}
}

View File

@@ -1,7 +1,12 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.*;
import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.Permission;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
@@ -43,4 +48,3 @@ public abstract class PermissionToPermissionDtoMapper {
target.add(linksBuilder.build());
}
}

View File

@@ -1,20 +1,31 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.*;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.AlreadyExistsException;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public class RepositoryCollectionResource {
private static final int DEFAULT_PAGE_SIZE = 10;
private final CollectionResourceManagerAdapter<Repository, RepositoryDto, RepositoryException> adapter;
private final CollectionResourceManagerAdapter<Repository, RepositoryDto> adapter;
private final RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper;
private final RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
private final ResourceLinks resourceLinks;
@@ -76,7 +87,7 @@ public class RepositoryCollectionResource {
})
@TypeHint(TypeHint.NO_CONTENT.class)
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
public Response create(RepositoryDto repositoryDto) throws RepositoryException {
public Response create(@Valid RepositoryDto repositoryDto) throws AlreadyExistsException {
return adapter.create(repositoryDto,
() -> dtoToRepositoryMapper.map(repositoryDto, null),
repository -> resourceLinks.repository().self(repository.getNamespace(), repository.getName()));

View File

@@ -5,7 +5,10 @@ import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -13,6 +16,7 @@ import java.util.Map;
@Getter @Setter
public class RepositoryDto extends HalRepresentation {
@Email
private String contact;
private Instant creationDate;
private String description;
@@ -20,8 +24,10 @@ public class RepositoryDto extends HalRepresentation {
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
private String namespace;
@Pattern(regexp = "(?!^\\.\\.$)(?!^\\.$)(?!.*[\\\\\\[\\]])^[A-z0-9\\.][A-z0-9\\.\\-_/]*$")
private String name;
private boolean archived = false;
@NotEmpty
private String type;
protected Map<String, String> properties;

View File

@@ -4,10 +4,9 @@ import org.mapstruct.*;
import sonia.scm.repository.Repository;
@Mapper
public abstract class RepositoryDtoToRepositoryMapper {
public abstract class RepositoryDtoToRepositoryMapper extends BaseDtoMapper {
@Mapping(target = "creationDate", ignore = true)
@Mapping(target = "lastModified", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "publicReadable", ignore = true)
@Mapping(target = "healthCheckFailures", ignore = true)

View File

@@ -1,49 +0,0 @@
/*
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.api.v2.resources;
import sonia.scm.api.rest.StatusExceptionMapper;
import sonia.scm.repository.RepositoryNotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
/**
* @since 2.0.0
*/
@Provider
public class RepositoryNotFoundExceptionMapper extends StatusExceptionMapper<RepositoryNotFoundException> {
public RepositoryNotFoundExceptionMapper() {
super(RepositoryNotFoundException.class, Response.Status.NOT_FOUND);
}
}

View File

@@ -3,15 +3,17 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.ConcurrentModificationException;
import sonia.scm.NotFoundException;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryIsNotArchivedException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
@@ -30,7 +32,7 @@ public class RepositoryResource {
private final RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
private final RepositoryManager manager;
private final SingleResourceManagerAdapter<Repository, RepositoryDto, RepositoryException> adapter;
private final SingleResourceManagerAdapter<Repository, RepositoryDto> adapter;
private final Provider<TagRootResource> tagRootResource;
private final Provider<BranchRootResource> branchRootResource;
private final Provider<ChangesetRootResource> changesetRootResource;
@@ -82,7 +84,7 @@ public class RepositoryResource {
@ResponseCode(code = 404, condition = "not found, no repository with the specified name available in the namespace"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name) {
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name) throws NotFoundException {
return adapter.get(loadBy(namespace, name), repositoryToDtoMapper::map);
}
@@ -129,7 +131,7 @@ public class RepositoryResource {
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, RepositoryDto repositoryDto) {
public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repositoryDto) throws NotFoundException, ConcurrentModificationException {
return adapter.update(
loadBy(namespace, name),
existing -> processUpdate(repositoryDto, existing),

View File

@@ -47,7 +47,7 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
}
}
linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName())));
linksBuilder.single(link("sources", resourceLinks.source().self(target.getNamespace(), target.getName())));
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName())));
target.add(linksBuilder.build());
}
}

View File

@@ -318,13 +318,29 @@ class ResourceLinks {
sourceLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, SourceRootResource.class);
}
String self(String namespace, String name) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("getAll").parameters().href();
String self(String namespace, String name, String revision) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("getAll").parameters(revision).href();
}
String selfWithoutRevision(String namespace, String name) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("getAllWithoutRevision").parameters().href();
}
public String source(String namespace, String name, String revision) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("get").parameters(revision).href();
}
public String sourceWithPath(String namespace, String name, String revision, String path) {
if (revision == null) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("get").parameters(null, path).href();
} else {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("get").parameters(revision, path).href();
}
}
public String content(String namespace, String name, String revision, String path) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("content").parameters().method("get").parameters(revision, path).href();
}
}
public PermissionLinks permission() {
return new PermissionLinks(uriInfoStore.get());
@@ -361,4 +377,37 @@ class ResourceLinks {
return permissionLinkBuilder.method("getRepositoryResource").parameters(repositoryNamespace, repositoryName).method("permissions").parameters().method(methodName).parameters(permissionName).href();
}
}
public UIPluginLinks uiPlugin() {
return new UIPluginLinks(uriInfoStore.get());
}
static class UIPluginLinks {
private final LinkBuilder uiPluginLinkBuilder;
UIPluginLinks(UriInfo uriInfo) {
uiPluginLinkBuilder = new LinkBuilder(uriInfo, UIRootResource.class, UIPluginResource.class);
}
String self(String id) {
return uiPluginLinkBuilder.method("plugins").parameters().method("getInstalledPlugin").parameters(id).href();
}
}
public UIPluginCollectionLinks uiPluginCollection() {
return new UIPluginCollectionLinks(uriInfoStore.get());
}
static class UIPluginCollectionLinks {
private final LinkBuilder uiPluginCollectionLinkBuilder;
UIPluginCollectionLinks(UriInfo uriInfo) {
uiPluginCollectionLinkBuilder = new LinkBuilder(uriInfo, UIRootResource.class, UIPluginResource.class);
}
String self() {
return uiPluginCollectionLinkBuilder.method("plugins").parameters().method("getInstalledPlugins").parameters().href();
}
}
}

View File

@@ -1,8 +1,10 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import sonia.scm.ConcurrentModificationException;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.NotFoundException;
import sonia.scm.api.rest.resources.AbstractManagerResource;
import javax.ws.rs.core.GenericEntity;
@@ -22,22 +24,20 @@ import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
*
* @param <MODEL_OBJECT> The type of the model object, eg. {@link sonia.scm.user.User}.
* @param <DTO> The corresponding transport object, eg. {@link UserDto}.
* @param <EXCEPTION> The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
*
* @see CollectionResourceManagerAdapter
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> extends AbstractManagerResource<MODEL_OBJECT, EXCEPTION> {
DTO extends HalRepresentation> extends AbstractManagerResource<MODEL_OBJECT> {
private final Function<Throwable, Optional<Response>> errorHandler;
SingleResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager, Class<MODEL_OBJECT> type) {
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
this(manager, type, e -> Optional.empty());
}
SingleResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager, Class<MODEL_OBJECT> type, Function<Throwable, Optional<Response>> errorHandler) {
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type, Function<Throwable, Optional<Response>> errorHandler) {
super(manager, type);
this.errorHandler = errorHandler;
}
@@ -46,28 +46,33 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
* Reads the model object for the given id, transforms it to a dto and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
Response get(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, DTO> mapToDto) {
Response get(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, DTO> mapToDto) throws NotFoundException {
return reader.get()
.map(mapToDto)
.map(Response::ok)
.map(Response.ResponseBuilder::build)
.orElse(Response.status(Response.Status.NOT_FOUND).build());
.orElseThrow(NotFoundException::new);
}
/**
* Update the model object for the given id according to the given function and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey) {
Optional<MODEL_OBJECT> existingModelObject = reader.get();
if (!existingModelObject.isPresent()) {
return Response.status(Response.Status.NOT_FOUND).build();
}
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject.get());
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey) throws NotFoundException, ConcurrentModificationException {
MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new);
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
if (!hasSameKey.test(changedModelObject)) {
return Response.status(BAD_REQUEST).entity("illegal change of id").build();
}
return update(getId(existingModelObject.get()), changedModelObject);
else if (modelObjectWasModifiedConcurrently(existingModelObject, changedModelObject)) {
throw new ConcurrentModificationException();
}
return update(getId(existingModelObject), changedModelObject);
}
private boolean modelObjectWasModifiedConcurrently(MODEL_OBJECT existing, MODEL_OBJECT updated) {
return existing.getLastModified() != null
&& (updated.getLastModified() == null || existing.getLastModified() > updated.getLastModified());
}
public Response delete(Supplier<Optional<MODEL_OBJECT>> reader) {

View File

@@ -1,25 +1,70 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.DefaultValue;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.BrowseCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.io.IOException;
public class SourceRootResource {
@GET
@Path("")
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
throw new UnsupportedOperationException();
private final RepositoryServiceFactory serviceFactory;
private final BrowserResultToBrowserResultDtoMapper browserResultToBrowserResultDtoMapper;
@Inject
public SourceRootResource(RepositoryServiceFactory serviceFactory, BrowserResultToBrowserResultDtoMapper browserResultToBrowserResultDtoMapper) {
this.serviceFactory = serviceFactory;
this.browserResultToBrowserResultDtoMapper = browserResultToBrowserResultDtoMapper;
}
@GET
@Produces(VndMediaType.SOURCE)
@Path("")
public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws RevisionNotFoundException, RepositoryNotFoundException, IOException {
return getSource(namespace, name, "/", null);
}
@GET
@Produces(VndMediaType.SOURCE)
@Path("{revision}")
public Response get() {
throw new UnsupportedOperationException();
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws RevisionNotFoundException, RepositoryNotFoundException, IOException {
return getSource(namespace, name, "/", revision);
}
@GET
@Produces(VndMediaType.SOURCE)
@Path("{revision}/{path: .*}")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) throws RevisionNotFoundException, RepositoryNotFoundException, IOException {
return getSource(namespace, name, path, revision);
}
private Response getSource(String namespace, String repoName, String path, String revision) throws IOException, RevisionNotFoundException, RepositoryNotFoundException {
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, repoName);
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
BrowseCommandBuilder browseCommand = repositoryService.getBrowseCommand();
browseCommand.setPath(path);
if (revision != null && !revision.isEmpty()) {
browseCommand.setRevision(revision);
}
BrowserResult browserResult = browseCommand.getBrowserResult();
if (browserResult != null) {
return Response.ok(browserResultToBrowserResultDtoMapper.map(browserResult, namespaceAndName)).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
}
}

View File

@@ -0,0 +1,14 @@
package sonia.scm.api.v2.resources;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class SubRepositoryDto {
private String repositoryUrl;
private String browserUrl;
private String revision;
}

View File

@@ -0,0 +1,25 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter @Setter @NoArgsConstructor
public class UIPluginDto extends HalRepresentation {
private String name;
private Iterable<String> bundles;
public UIPluginDto(String name, Iterable<String> bundles) {
this.name = name;
this.bundles = bundles;
}
@Override
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,46 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import sonia.scm.plugin.PluginWrapper;
import java.util.Collection;
import java.util.List;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Links.linkingTo;
import static java.util.stream.Collectors.toList;
public class UIPluginDtoCollectionMapper {
private final ResourceLinks resourceLinks;
private final UIPluginDtoMapper mapper;
@Inject
public UIPluginDtoCollectionMapper(ResourceLinks resourceLinks, UIPluginDtoMapper mapper) {
this.resourceLinks = resourceLinks;
this.mapper = mapper;
}
public HalRepresentation map(Collection<PluginWrapper> plugins) {
List<UIPluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
return new HalRepresentation(createLinks(), embedDtos(dtos));
}
private Links createLinks() {
String baseUrl = resourceLinks.uiPluginCollection().self();
Links.Builder linksBuilder = linkingTo()
.with(Links.linkingTo().self(baseUrl).build());
return linksBuilder.build();
}
private Embedded embedDtos(List<UIPluginDto> dtos) {
return embeddedBuilder()
.with("plugins", dtos)
.build();
}
}

View File

@@ -0,0 +1,61 @@
package sonia.scm.api.v2.resources;
import com.google.common.base.Strings;
import de.otto.edison.hal.Links;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.util.HttpUtil;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import static de.otto.edison.hal.Links.linkingTo;
public class UIPluginDtoMapper {
private final ResourceLinks resourceLinks;
private final HttpServletRequest request;
@Inject
public UIPluginDtoMapper(ResourceLinks resourceLinks, HttpServletRequest request) {
this.resourceLinks = resourceLinks;
this.request = request;
}
public UIPluginDto map(PluginWrapper plugin) {
UIPluginDto dto = new UIPluginDto(
plugin.getPlugin().getInformation().getName(),
getScriptResources(plugin)
);
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.uiPlugin()
.self(plugin.getId()));
dto.add(linksBuilder.build());
return dto;
}
private Set<String> getScriptResources(PluginWrapper wrapper) {
Set<String> scriptResources = wrapper.getPlugin().getResources().getScriptResources();
if (scriptResources != null) {
return scriptResources.stream()
.map(this::addContextPath)
.collect(Collectors.toSet());
}
return Collections.emptySet();
}
private String addContextPath(String resource) {
String ctxPath = request.getContextPath();
if (Strings.isNullOrEmpty(ctxPath)) {
return resource;
}
return HttpUtil.append(ctxPath, resource);
}
}

View File

@@ -0,0 +1,90 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.web.VndMediaType;
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.Response;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class UIPluginResource {
private final PluginLoader pluginLoader;
private final UIPluginDtoCollectionMapper collectionMapper;
private final UIPluginDtoMapper mapper;
@Inject
public UIPluginResource(PluginLoader pluginLoader, UIPluginDtoCollectionMapper collectionMapper, UIPluginDtoMapper mapper) {
this.pluginLoader = pluginLoader;
this.collectionMapper = collectionMapper;
this.mapper = mapper;
}
/**
* Returns a collection of installed plugins and their ui bundles.
*
* @return collection of installed plugins.
*/
@GET
@Path("")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(CollectionDto.class)
@Produces(VndMediaType.UI_PLUGIN_COLLECTION)
public Response getInstalledPlugins() {
List<PluginWrapper> plugins = pluginLoader.getInstalledPlugins()
.stream()
.filter(this::filter)
.collect(Collectors.toList());
return Response.ok(collectionMapper.map(plugins)).build();
}
/**
* Returns the installed plugin with the given id.
*
* @param id id of plugin
*
* @return installed plugin with specified id
*/
@GET
@Path("{id}")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 404, condition = "not found"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(UIPluginDto.class)
@Produces(VndMediaType.UI_PLUGIN)
public Response getInstalledPlugin(@PathParam("id") String id) {
Optional<UIPluginDto> uiPluginDto = pluginLoader.getInstalledPlugins()
.stream()
.filter(this::filter)
.filter(plugin -> id.equals(plugin.getId()))
.map(mapper::map)
.findFirst();
if (uiPluginDto.isPresent()) {
return Response.ok(uiPluginDto.get()).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
private boolean filter(PluginWrapper plugin) {
return plugin.getPlugin().getResources() != null;
}
}

View File

@@ -0,0 +1,22 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
@Path("v2/ui")
public class UIRootResource {
private Provider<UIPluginResource> uiPluginResourceProvider;
@Inject
public UIRootResource(Provider<UIPluginResource> uiPluginResourceProvider) {
this.uiPluginResourceProvider = uiPluginResourceProvider;
}
@Path("plugins")
public UIPluginResource plugins() {
return uiPluginResourceProvider.get();
}
}

View File

@@ -1,15 +1,25 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.*;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.AlreadyExistsException;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.io.IOException;
public class UserCollectionResource {
@@ -18,7 +28,7 @@ public class UserCollectionResource {
private final UserCollectionToDtoMapper userCollectionToDtoMapper;
private final ResourceLinks resourceLinks;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
private final IdResourceManagerAdapter<User, UserDto> adapter;
@Inject
public UserCollectionResource(UserManager manager, UserDtoToUserMapper dtoToUserMapper,
@@ -78,7 +88,7 @@ public class UserCollectionResource {
})
@TypeHint(TypeHint.NO_CONTENT.class)
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
public Response create(UserDto userDto) throws IOException, UserException {
public Response create(@Valid UserDto userDto) throws AlreadyExistsException {
return adapter.create(userDto,
() -> dtoToUserMapper.map(userDto, ""),
user -> resourceLinks.user().self(user.getName()));

View File

@@ -6,7 +6,10 @@ import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.time.Instant;
import java.util.Map;
@@ -15,10 +18,13 @@ public class UserDto extends HalRepresentation {
private boolean active;
private boolean admin;
private Instant creationDate;
@NotEmpty
private String displayName;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
@NotEmpty @Email
private String mail;
@Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$")
private String name;
private String password;
private String type;

View File

@@ -9,19 +9,20 @@ import sonia.scm.user.User;
import javax.inject.Inject;
import java.time.Instant;
import static sonia.scm.api.rest.resources.UserResource.DUMMY_PASSWORT;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class UserDtoToUserMapper {
public abstract class UserDtoToUserMapper extends BaseDtoMapper {
@Inject
private PasswordService passwordService;
@Mapping(source = "password", target = "password", qualifiedByName = "encrypt")
@Mapping(target = "creationDate", ignore = true)
@Mapping(target = "lastModified", ignore = true)
public abstract User map(UserDto userDto, @Context String originalPassword);
@Named("encrypt")

View File

@@ -3,13 +3,21 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.ConcurrentModificationException;
import sonia.scm.NotFoundException;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
public class UserResource {
@@ -17,7 +25,7 @@ public class UserResource {
private final UserDtoToUserMapper dtoToUserMapper;
private final UserToUserDtoMapper userToDtoMapper;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
private final IdResourceManagerAdapter<User, UserDto> adapter;
@Inject
public UserResource(UserDtoToUserMapper dtoToUserMapper, UserToUserDtoMapper userToDtoMapper, UserManager manager) {
@@ -45,7 +53,7 @@ public class UserResource {
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("id") String id) {
public Response get(@PathParam("id") String id) throws NotFoundException {
return adapter.get(id, userToDtoMapper::map);
}
@@ -90,7 +98,7 @@ public class UserResource {
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response update(@PathParam("id") String name, UserDto userDto) {
public Response update(@PathParam("id") String name, @Valid UserDto userDto) throws NotFoundException, ConcurrentModificationException {
return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
}
}

View File

@@ -35,19 +35,18 @@ package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Iterators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import static java.util.Collections.emptyIterator;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
@@ -109,7 +108,7 @@ public final class CacheConfigurations
if (it == null)
{
it = Iterators.emptyIterator();
it = emptyIterator();
}
return it;

View File

@@ -34,17 +34,16 @@ package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
//~--- JDK imports ------------------------------------------------------------
import java.io.Serializable;
import com.google.common.base.MoreObjects;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.io.Serializable;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -70,7 +69,7 @@ public class GuavaCacheConfiguration implements Serializable
public String toString()
{
//J-
return Objects.toStringHelper(this)
return MoreObjects.toStringHelper(this)
.add("concurrencyLevel", concurrencyLevel)
.add("copyStrategy", copyStrategy)
.add("expireAfterAccess", expireAfterAccess)

View File

@@ -62,7 +62,8 @@ import javax.servlet.http.HttpServletResponse;
* @author Sebastian Sdorra
*/
@Priority(Filters.PRIORITY_AUTHORIZATION)
@WebElement(value = Filters.PATTERN_RESTAPI, morePatterns = { Filters.PATTERN_DEBUG })
// TODO find a better way for unprotected resources
@WebElement(value = "/api/rest/(?!v2/ui).*", regex = true)
public class SecurityFilter extends HttpFilter
{

View File

@@ -42,8 +42,10 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.AlreadyExistsException;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.TransformFilter;
import sonia.scm.search.SearchRequest;
@@ -52,7 +54,12 @@ import sonia.scm.util.CollectionAppender;
import sonia.scm.util.Util;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
//~--- JDK imports ------------------------------------------------------------
@@ -80,10 +87,7 @@ public class DefaultGroupManager extends AbstractGroupManager
public DefaultGroupManager(GroupDAO groupDAO)
{
this.groupDAO = groupDAO;
this.managerDaoAdapter = new ManagerDaoAdapter<>(
groupDAO,
GroupNotFoundException::new,
GroupAlreadyExistsException::new);
this.managerDaoAdapter = new ManagerDaoAdapter<>(groupDAO);
}
//~--- methods --------------------------------------------------------------
@@ -102,7 +106,7 @@ public class DefaultGroupManager extends AbstractGroupManager
}
@Override
public Group create(Group group) throws GroupException {
public Group create(Group group) throws AlreadyExistsException {
String type = group.getType();
if (Util.isEmpty(type)) {
group.setType(groupDAO.getType());
@@ -121,7 +125,7 @@ public class DefaultGroupManager extends AbstractGroupManager
}
@Override
public void delete(Group group) throws GroupException {
public void delete(Group group) throws NotFoundException {
logger.info("delete group {} of type {}", group.getName(), group.getType());
managerDaoAdapter.delete(
group,
@@ -140,17 +144,8 @@ public class DefaultGroupManager extends AbstractGroupManager
@Override
public void init(SCMContextProvider context) {}
/**
* Method description
*
*
* @param group
*
* @throws GroupException
* @throws IOException
*/
@Override
public void modify(Group group) throws GroupException {
public void modify(Group group) throws NotFoundException {
logger.info("modify group {} of type {}", group.getName(), group.getType());
managerDaoAdapter.modify(
@@ -164,18 +159,8 @@ public class DefaultGroupManager extends AbstractGroupManager
);
}
/**
* Method description
*
*
* @param group
*
* @throws GroupException
* @throws IOException
*/
@Override
public void refresh(Group group) throws GroupException
{
public void refresh(Group group) throws NotFoundException {
String name = group.getName();
if (logger.isInfoEnabled())
{
@@ -187,7 +172,7 @@ public class DefaultGroupManager extends AbstractGroupManager
if (fresh == null)
{
throw new GroupNotFoundException(group);
throw new NotFoundException("group", group.getId());
}
fresh.copyProperties(group);
@@ -400,5 +385,5 @@ public class DefaultGroupManager extends AbstractGroupManager
/** Field description */
private GroupDAO groupDAO;
private final ManagerDaoAdapter<Group, GroupException> managerDaoAdapter;
private final ManagerDaoAdapter<Group> managerDaoAdapter;
}

View File

@@ -39,18 +39,18 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.ServletContext;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import javax.servlet.ServletContext;
//~--- JDK imports ------------------------------------------------------------
/**
* Default implementation of the {@link UberWebResourceLoader}.
@@ -133,7 +133,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
try
{
URL ctxResource = servletContext.getResource(path);
URL ctxResource = nonDirectory(servletContext.getResource(path));
if (ctxResource != null)
{
@@ -143,7 +143,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
for (PluginWrapper wrapper : plugins)
{
URL resource = wrapper.getWebResourceLoader().getResource(path);
URL resource = nonDirectory(wrapper.getWebResourceLoader().getResource(path));
if (resource != null)
{
@@ -185,17 +185,17 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
*/
private URL find(String path)
{
URL resource = null;
URL resource;
try
{
resource = servletContext.getResource(path);
resource = nonDirectory(servletContext.getResource(path));
if (resource == null)
{
for (PluginWrapper wrapper : plugins)
{
resource = wrapper.getWebResourceLoader().getResource(path);
resource = nonDirectory(wrapper.getWebResourceLoader().getResource(path));
if (resource != null)
{
@@ -218,6 +218,29 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
return resource;
}
private URL nonDirectory(URL url) {
if (url == null) {
return null;
}
if (isDirectory(url)) {
return null;
}
return url;
}
private boolean isDirectory(URL url) {
if ("file".equals(url.getProtocol())) {
try {
return Files.isDirectory(Paths.get(url.toURI()));
} catch (URISyntaxException ex) {
throw Throwables.propagate(ex);
}
}
return false;
}
//~--- fields ---------------------------------------------------------------
/** Field description */

View File

@@ -93,7 +93,7 @@ public class PathWebResourceLoader implements WebResourceLoader
URL resource = null;
Path file = directory.resolve(filePath(path));
if (Files.exists(file))
if (Files.exists(file) && ! Files.isDirectory(file))
{
logger.trace("found path {} at {}", path, file);

View File

@@ -42,10 +42,12 @@ import com.google.inject.Singleton;
import org.apache.shiro.concurrent.SubjectAwareExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.AlreadyExistsException;
import sonia.scm.ArgumentIsInvalidException;
import sonia.scm.ConfigurationException;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.Type;
import sonia.scm.config.ScmConfiguration;
@@ -57,7 +59,6 @@ import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -91,7 +92,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private final Set<Type> types;
private RepositoryMatcher repositoryMatcher;
private NamespaceStrategy namespaceStrategy;
private final ManagerDaoAdapter<Repository, RepositoryException> managerDaoAdapter;
private final ManagerDaoAdapter<Repository> managerDaoAdapter;
@Inject
@@ -118,10 +119,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
for (RepositoryHandler handler : handlerSet) {
addHandler(contextProvider, handler);
}
managerDaoAdapter = new ManagerDaoAdapter<>(
repositoryDAO,
RepositoryNotFoundException::new,
RepositoryAlreadyExistsException::create);
managerDaoAdapter = new ManagerDaoAdapter<>(repositoryDAO);
}
@@ -135,22 +133,26 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}
@Override
public Repository create(Repository repository) throws RepositoryException {
public Repository create(Repository repository) throws AlreadyExistsException {
return create(repository, true);
}
public Repository create(Repository repository, boolean initRepository) throws RepositoryException {
public Repository create(Repository repository, boolean initRepository) throws AlreadyExistsException {
repository.setId(keyGenerator.createKey());
repository.setNamespace(namespaceStrategy.createNamespace(repository));
logger.info("create repository {} of type {} in namespace {}", repository.getName(), repository.getType(), repository.getNamespace());
logger.info("create repository {}/{} of type {} in namespace {}", repository.getNamespace(), repository.getName(), repository.getType(), repository.getNamespace());
return managerDaoAdapter.create(
repository,
RepositoryPermissions::create,
newRepository -> {
if (initRepository) {
getHandler(newRepository).create(newRepository);
try {
getHandler(newRepository).create(newRepository);
} catch (AlreadyExistsException e) {
throw new InternalRepositoryException("directory for repository does already exist", e);
}
}
fireEvent(HandlerEventType.BEFORE_CREATE, newRepository);
},
@@ -160,8 +162,8 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}
@Override
public void delete(Repository repository) throws RepositoryException {
logger.info("delete repository {} of type {}", repository.getName(), repository.getType());
public void delete(Repository repository) throws NotFoundException {
logger.info("delete repository {}/{} of type {}", repository.getNamespace(), repository.getName(), repository.getType());
managerDaoAdapter.delete(
repository,
() -> RepositoryPermissions.delete(repository),
@@ -170,17 +172,16 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
);
}
private void preDelete(Repository toDelete) throws RepositoryException {
private void preDelete(Repository toDelete) {
if (configuration.isEnableRepositoryArchive() && !toDelete.isArchived()) {
throw new RepositoryIsNotArchivedException();
}
fireEvent(HandlerEventType.BEFORE_DELETE, toDelete);
getHandler(toDelete).delete(toDelete);
// getHandler(toDelete).delete(toDelete);
}
@Override
public void importRepository(Repository repository)
throws RepositoryException, IOException {
public void importRepository(Repository repository) throws AlreadyExistsException {
create(repository, false);
}
@@ -189,23 +190,26 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}
@Override
public void modify(Repository repository) throws RepositoryException {
logger.info("modify repository {} of type {}", repository.getName(), repository.getType());
public void modify(Repository repository) throws NotFoundException {
logger.info("modify repository {}/{} of type {}", repository.getNamespace(), repository.getName(), repository.getType());
managerDaoAdapter.modify(
repository,
RepositoryPermissions::modify,
notModified -> {
fireEvent(HandlerEventType.BEFORE_MODIFY, repository, notModified);
getHandler(repository).modify(repository);
try {
getHandler(repository).modify(repository);
} catch (NotFoundException e) {
throw new IllegalStateException("repository not found though just created", e);
}
},
notModified -> fireEvent(HandlerEventType.MODIFY, repository, notModified)
);
}
@Override
public void refresh(Repository repository)
throws RepositoryException {
public void refresh(Repository repository) throws RepositoryNotFoundException {
AssertUtil.assertIsNotNull(repository);
RepositoryPermissions.read(repository).check();
@@ -403,7 +407,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
if (handlerMap.containsKey(type.getName())) {
throw new ConfigurationException(
type.getName().concat("allready registered"));
type.getName().concat("already registered"));
}
if (logger.isInfoEnabled()) {
@@ -417,15 +421,14 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}
private RepositoryHandler getHandler(Repository repository)
throws RepositoryException {
{
String type = repository.getType();
RepositoryHandler handler = handlerMap.get(type);
if (handler == null) {
throw new RepositoryHandlerNotFoundException(
"could not find handler for ".concat(type));
throw new InternalRepositoryException("could not find handler for " + type);
} else if (!handler.isConfigured()) {
throw new RepositoryException("handler is not configured");
throw new InternalRepositoryException("handler is not configured for type " + type);
}
return handler;

View File

@@ -1,9 +1,9 @@
/**
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
*
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* <p>
* 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
@@ -11,7 +11,7 @@
* 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.
*
* <p>
* 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
@@ -22,81 +22,45 @@
* 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.
*
* <p>
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.github.sdorra.ssp.PermissionActionCheck;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import sonia.scm.ConcurrentModificationException;
import sonia.scm.NotFoundException;
import java.util.Set;
/**
*
* @author Sebastian Sdorra
*/
public final class HealthChecker
{
/**
* the logger for HealthChecker
*/
public final class HealthChecker {
private static final Logger logger =
LoggerFactory.getLogger(HealthChecker.class);
//~--- constructors ---------------------------------------------------------
private final Set<HealthCheck> checks;
private final RepositoryManager repositoryManager;
/**
* Constructs ...
*
*
* @param checks
* @param repositoryManager
*/
@Inject
public HealthChecker(Set<HealthCheck> checks,
RepositoryManager repositoryManager)
{
RepositoryManager repositoryManager) {
this.checks = checks;
this.repositoryManager = repositoryManager;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param id
*
*
* @throws IOException
* @throws RepositoryException
* @throws RepositoryNotFoundException
*/
public void check(String id)
throws RepositoryNotFoundException, RepositoryException, IOException
{
public void check(String id) throws NotFoundException {
RepositoryPermissions.healthCheck(id).check();
Repository repository = repositoryManager.get(id);
if (repository == null)
{
if (repository == null) {
throw new RepositoryNotFoundException(
"could not find repository with id ".concat(id));
}
@@ -104,49 +68,26 @@ public final class HealthChecker
doCheck(repository);
}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
public void check(Repository repository)
throws RepositoryException, IOException
{
throws NotFoundException, ConcurrentModificationException {
RepositoryPermissions.healthCheck(repository).check();
doCheck(repository);
}
/**
* Method description
*
*
*/
public void checkAll()
{
public void checkAll() {
logger.debug("check health of all repositories");
PermissionActionCheck<Repository> check = RepositoryPermissions.healthCheck();
for (Repository repository : repositoryManager.getAll())
{
if (check.isPermitted(repository))
{
try
{
for (Repository repository : repositoryManager.getAll()) {
if (check.isPermitted(repository)) {
try {
check(repository);
}
catch (RepositoryException | IOException ex)
{
} catch (ConcurrentModificationException | NotFoundException ex) {
logger.error("health check ends with exception", ex);
}
}
else
{
} else {
logger.debug(
"no permissions to execute health check for repository {}",
repository.getId());
@@ -154,32 +95,25 @@ public final class HealthChecker
}
}
private void doCheck(Repository repository)
throws RepositoryException, IOException
{
private void doCheck(Repository repository) throws NotFoundException {
logger.info("start health check for repository {}", repository.getName());
HealthCheckResult result = HealthCheckResult.healthy();
for (HealthCheck check : checks)
{
for (HealthCheck check : checks) {
logger.trace("execute health check {} for repository {}",
check.getClass(), repository.getName());
result = result.merge(check.check(repository));
}
if (result.isUnhealthy())
{
if (result.isUnhealthy()) {
logger.warn("repository {} is unhealthy: {}", repository.getName(),
result);
}
else
{
} else {
logger.info("repository {} is healthy", repository.getName());
}
if (!(repository.isHealthy() && result.isHealthy()))
{
if (!(repository.isHealthy() && result.isHealthy())) {
logger.trace("store health check results for repository {}",
repository.getName());
repository.setHealthCheckFailures(
@@ -188,11 +122,5 @@ public final class HealthChecker
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final Set<HealthCheck> checks;
/** Field description */
private final RepositoryManager repositoryManager;
}

View File

@@ -38,6 +38,7 @@ import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;
import sonia.scm.NotFoundException;
import sonia.scm.plugin.Extension;
import sonia.scm.web.security.AdministrationContext;
import sonia.scm.web.security.PrivilegedAction;
@@ -146,13 +147,10 @@ public final class LastModifiedUpdateListener
logger.info("update last modified date of repository {}", dbr.getId());
dbr.setLastModified(System.currentTimeMillis());
try
{
try {
repositoryManager.modify(dbr);
}
catch (RepositoryException ex)
{
logger.error("could not modify repository", ex);
} catch (NotFoundException e) {
logger.error("could not modify repository", e);
}
}
else

View File

@@ -1,215 +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.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
/**
*
* @author Sebastian Sdorra
*/
public abstract class AbstractResource implements Resource
{
/**
* the logger for AbstractResource
*/
private static final Logger logger =
LoggerFactory.getLogger(AbstractResource.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
* @param pluginLoader
* @param resources
* @param resourceHandlers
*/
public AbstractResource(PluginLoader pluginLoader, List<String> resources,
List<ResourceHandler> resourceHandlers)
{
this.pluginLoader = pluginLoader;
this.resources = resources;
this.resourceHandlers = resourceHandlers;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param stream
*
* @throws IOException
*/
protected void appendResources(OutputStream stream) throws IOException
{
if (Util.isNotEmpty(resources))
{
for (String resource : resources)
{
appendResource(stream, resource);
}
}
if (Util.isNotEmpty(resourceHandlers))
{
Collections.sort(resourceHandlers, new ResourceHandlerComparator());
for (ResourceHandler resourceHandler : resourceHandlers)
{
processResourceHandler(stream, resourceHandler);
}
}
}
/**
* Method description
*
*
* @param stream
* @param path
*
* @throws IOException
*/
private void appendResource(OutputStream stream, String path)
throws IOException
{
URL resource = getResourceAsURL(path);
if (resource != null)
{
Resources.copy(resource, stream);
}
else if (logger.isWarnEnabled())
{
logger.warn("could not find resource {}", path);
}
}
/**
* Method description
*
*
* @param stream
* @param resourceHandler
*
* @throws IOException
*/
private void processResourceHandler(OutputStream stream,
ResourceHandler resourceHandler)
throws IOException
{
if (resourceHandler.getType() == getType())
{
if (logger.isTraceEnabled())
{
logger.trace("process resource handler {}", resourceHandler.getClass());
}
URL resource = resourceHandler.getResource();
if (resource != null)
{
Resources.copy(resource, stream);
}
else if (logger.isDebugEnabled())
{
logger.debug("resource handler {} does not return a resource",
resourceHandler.getClass());
}
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param path
*
* @return
*/
private URL getResourceAsURL(String path)
{
URL resource = null;
ClassLoader classLoader = pluginLoader.getUberClassLoader();
if (classLoader != null)
{
String classLoaderResource = path;
if (classLoaderResource.startsWith("/"))
{
classLoaderResource = classLoaderResource.substring(1);
}
resource = classLoader.getResource(classLoaderResource);
}
return resource;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
protected final List<ResourceHandler> resourceHandlers;
/** Field description */
protected final List<String> resources;
/** Field description */
private final PluginLoader pluginLoader;
}

View File

@@ -1,308 +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.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginResources;
//~--- JDK imports ------------------------------------------------------------
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.ServletContext;
import sonia.scm.plugin.PluginWrapper;
/**
*
* @author Sebastian Sdorra
*/
public abstract class AbstractResourceManager implements ResourceManager
{
/**
* Constructs ...
*
* @param pluginLoader
* @param resourceHandlers
*/
protected AbstractResourceManager(PluginLoader pluginLoader,
Set<ResourceHandler> resourceHandlers)
{
this.pluginLoader = pluginLoader;
this.resourceHandlers = resourceHandlers;
collectResources(resourceMap);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resourceMap
*/
protected abstract void collectResources(Map<ResourceKey,
Resource> resourceMap);
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param type
* @param name
*
* @return
*/
@Override
public Resource getResource(ResourceType type, String name)
{
return resourceMap.get(new ResourceKey(name, type));
}
/**
* Method description
*
*
* @param type
*
* @return
*/
@Override
public List<Resource> getResources(ResourceType type)
{
List<Resource> resources = new ArrayList<>();
for (Entry<ResourceKey, Resource> e : resourceMap.entrySet())
{
if (e.getKey().getType() == type)
{
resources.add(e.getValue());
}
}
Collections.sort(resources, ResourceNameComparator.INSTANCE);
return resources;
}
/**
* Method description
*
*
* @return
*/
protected List<String> getScriptResources()
{
List<String> resources = new ArrayList<>();
Collection<PluginWrapper> wrappers = pluginLoader.getInstalledPlugins();
if (wrappers != null)
{
for (PluginWrapper plugin : wrappers)
{
processPlugin(resources, plugin.getPlugin());
}
}
// fix order of script resources, see https://goo.gl/ok03l4
Collections.sort(resources);
return resources;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resources
* @param plugin
*/
private void processPlugin(List<String> resources, Plugin plugin)
{
PluginResources pluginResources = plugin.getResources();
if (pluginResources != null)
{
Set<String> scriptResources = pluginResources.getScriptResources();
if (scriptResources != null)
{
resources.addAll(scriptResources);
}
}
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 12/02/03
* @author Enter your name here...
*/
protected static class ResourceKey
{
/**
* Constructs ...
*
*
* @param name
* @param type
*/
public ResourceKey(String name, ResourceType type)
{
this.name = name;
this.type = type;
}
//~--- methods ------------------------------------------------------------
/**
* Method description
*
*
* @param obj
*
* @return
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final ResourceKey other = (ResourceKey) obj;
if ((this.name == null)
? (other.name != null)
: !this.name.equals(other.name))
{
return false;
}
return this.type == other.type;
}
/**
* Method description
*
*
* @return
*/
@Override
public int hashCode()
{
int hash = 7;
hash = 53 * hash + ((this.name != null)
? this.name.hashCode()
: 0);
hash = 53 * hash + ((this.type != null)
? this.type.hashCode()
: 0);
return hash;
}
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
public ResourceType getType()
{
return type;
}
//~--- fields -------------------------------------------------------------
/** Field description */
private final String name;
/** Field description */
private final ResourceType type;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
protected PluginLoader pluginLoader;
/** Field description */
protected Set<ResourceHandler> resourceHandlers;
/** Field description */
protected Map<ResourceKey, Resource> resourceMap = new HashMap<>();
/** Field description */
protected ServletContext servletContext;
}

View File

@@ -1,165 +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.resources;
//~--- non-JDK imports --------------------------------------------------------
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author Sebastian Sdorra
*/
public abstract class AbstractResourceServlet extends HttpServlet
{
/** Field description */
private static final long serialVersionUID = -1774434741744054387L;
/**
* the logger for AbstractResourceServlet
*/
private static final Logger logger =
LoggerFactory.getLogger(AbstractResourceServlet.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param resourceManager
*/
public AbstractResourceServlet(ResourceManager resourceManager)
{
this.resourceManager = resourceManager;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
protected abstract ResourceType getType();
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param response
*
* @throws IOException
* @throws ServletException
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String uri = HttpUtil.getStrippedURI(request);
ResourceType type = getType();
String nameSeparator = HttpUtil.SEPARATOR_PATH.concat(
type.getExtension()).concat(
HttpUtil.SEPARATOR_PATH);
int index = uri.indexOf(nameSeparator);
if (index > 0)
{
String name = uri.substring(index + nameSeparator.length());
Resource resource = resourceManager.getResource(type, name);
if (resource != null)
{
printResource(response, resource);
}
else
{
if (logger.isWarnEnabled())
{
logger.warn("no resource with type {} and name {} found", type, name);
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
else
{
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
/**
* Method description
*
*
* @param response
* @param resource
*
* @throws IOException
*/
private void printResource(HttpServletResponse response, Resource resource)
throws IOException
{
response.setContentType(resource.getType().getContentType());
try (OutputStream output = response.getOutputStream())
{
resource.copyTo(output);
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ResourceManager resourceManager;
}

View File

@@ -1,135 +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.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.plugin.PluginLoader;
import sonia.scm.util.ChecksumUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
*
* @author Sebastian Sdorra
*/
public final class DefaultResource extends AbstractResource
{
/**
* Constructs ...
*
*
* @param pluginLoader
* @param resources
* @param resourceHandlers
* @param type
*
* @throws IOException
*/
public DefaultResource(PluginLoader pluginLoader, List<String> resources,
List<ResourceHandler> resourceHandlers, ResourceType type)
throws IOException
{
super(pluginLoader, resources, resourceHandlers);
this.type = type;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
{
appendResources(baos);
this.content = baos.toByteArray();
this.name = ChecksumUtil.createChecksum(this.content).concat(".").concat(
type.getExtension());
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param output
*
* @throws IOException
*/
@Override
public void copyTo(OutputStream output) throws IOException
{
output.write(content);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
@Override
public ResourceType getType()
{
return type;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final byte[] content;
/** Field description */
private final String name;
/** Field description */
private final ResourceType type;
}

View File

@@ -1,111 +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.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.PluginLoader;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class DefaultResourceManager extends AbstractResourceManager
{
/**
* the logger for DefaultResourceManager
*/
private static final Logger logger =
LoggerFactory.getLogger(DefaultResourceManager.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
* @param pluginLoader
* @param resourceHandlers
*/
@Inject
public DefaultResourceManager(PluginLoader pluginLoader,
Set<ResourceHandler> resourceHandlers)
{
super(pluginLoader, resourceHandlers);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resourceMap
*/
@Override
protected void collectResources(Map<ResourceKey, Resource> resourceMap)
{
List<String> resources = getScriptResources();
try
{
Resource resource = new DefaultResource(pluginLoader, resources,
Lists.newArrayList(resourceHandlers),
ResourceType.SCRIPT);
resourceMap.put(new ResourceKey(resource.getName(), ResourceType.SCRIPT),
resource);
}
catch (IOException ex)
{
logger.error("could not collect resources", ex);
}
}
}

View File

@@ -1,135 +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.resources;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.plugin.PluginLoader;
import sonia.scm.util.HttpUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
*
* @author Sebastian Sdorra
*/
public final class DevelopmentResource extends AbstractResource
{
/**
* Constructs ...
*
*
* @param pluginLoader
* @param resources
* @param resourceHandlers
* @param name
* @param type
*/
public DevelopmentResource(PluginLoader pluginLoader, List<String> resources,
List<ResourceHandler> resourceHandlers, String name, ResourceType type)
{
super(pluginLoader, resources, resourceHandlers);
this.type = type;
if (name.startsWith(HttpUtil.SEPARATOR_PATH))
{
name = name.substring(1);
}
String ext = ".".concat(type.getExtension());
if (!name.endsWith(ext))
{
name = name.concat(ext);
}
this.name = name;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param output
*
* @throws IOException
*/
@Override
public void copyTo(OutputStream output) throws IOException
{
appendResources(output);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
@Override
public ResourceType getType()
{
return type;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final String name;
/** Field description */
private final ResourceType type;
}

View File

@@ -1,117 +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.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Singleton;
import sonia.scm.plugin.PluginLoader;
//~--- JDK imports ------------------------------------------------------------
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class DevelopmentResourceManager extends AbstractResourceManager
{
/** Field description */
public static final String PREFIX_HANDLER = "handler/";
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param pluginLoader
* @param resourceHandlers
*/
@Inject
public DevelopmentResourceManager(PluginLoader pluginLoader,
Set<ResourceHandler> resourceHandlers)
{
super(pluginLoader, resourceHandlers);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param resourceMap
*/
@Override
@SuppressWarnings("unchecked")
protected void collectResources(Map<ResourceKey, Resource> resourceMap)
{
List<String> scripts = getScriptResources();
for (String script : scripts)
{
Resource resource = new DevelopmentResource(pluginLoader,
Arrays.asList(script), Collections.EMPTY_LIST,
script, ResourceType.SCRIPT);
resourceMap.put(new ResourceKey(resource.getName(), ResourceType.SCRIPT),
resource);
}
for (ResourceHandler handler : resourceHandlers)
{
String name = handler.getName();
if (name.startsWith("/"))
{
name = name.substring(1);
}
name = PREFIX_HANDLER.concat(name);
resourceMap.put(new ResourceKey(name, ResourceType.SCRIPT),
new DevelopmentResource(pluginLoader, Collections.EMPTY_LIST,
Arrays.asList(handler), name, ResourceType.SCRIPT));
}
}
}

View File

@@ -1,78 +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.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class ScriptResourceServlet extends AbstractResourceServlet
{
/** Field description */
private static final long serialVersionUID = 1279211769033477225L;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param resourceManager
*/
@Inject
public ScriptResourceServlet(ResourceManager resourceManager)
{
super(resourceManager);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
protected ResourceType getType()
{
return ResourceType.SCRIPT;
}
}

View File

@@ -31,20 +31,19 @@
package sonia.scm.security;
import com.google.common.annotations.VisibleForTesting;
import java.util.Date;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Generates cookies and invalidates access token cookies.
@@ -81,7 +80,7 @@ public final class AccessTokenCookieIssuer {
public void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken) {
LOG.trace("create and attach cookie for access token {}", accessToken.getId());
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, accessToken.compact());
c.setPath(request.getContextPath());
c.setPath(contextPath(request));
c.setMaxAge(getMaxAge(accessToken));
c.setHttpOnly(isHttpOnly());
c.setSecure(isSecure(request));
@@ -100,7 +99,7 @@ public final class AccessTokenCookieIssuer {
LOG.trace("invalidates access token cookie");
Cookie c = new Cookie(HttpUtil.COOKIE_BEARER_AUTHENTICATION, Util.EMPTY_STRING);
c.setPath(request.getContextPath());
c.setPath(contextPath(request));
c.setMaxAge(0);
c.setHttpOnly(isHttpOnly());
c.setSecure(isSecure(request));
@@ -108,6 +107,15 @@ public final class AccessTokenCookieIssuer {
// attach empty cookie, that the browser can remove it
response.addCookie(c);
}
@VisibleForTesting
String contextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
if (Strings.isNullOrEmpty(contextPath)) {
return "/";
}
return contextPath;
}
private int getMaxAge(AccessToken accessToken){
long maxAgeMs = accessToken.getExpiration().getTime() - new Date().getTime();

View File

@@ -30,12 +30,8 @@
*/
package sonia.scm.security;
import com.google.common.base.Objects;
import com.google.common.base.MoreObjects;
import com.google.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import javax.inject.Singleton;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
@@ -44,6 +40,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import javax.inject.Singleton;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* Configurable implementation of {@link LoginAttemptHandler}.
*
@@ -175,7 +176,7 @@ public class ConfigurableLoginAttemptHandler implements LoginAttemptHandler {
@Override
public String toString() {
return Objects.toStringHelper(this)
return MoreObjects.toStringHelper(this)
.add("counter", counter)
.add("lastAttempt", lastAttempt)
.toString();

View File

@@ -36,23 +36,19 @@ package sonia.scm.security;
//~--- non-JDK imports --------------------------------------------------------
import com.github.legman.Subscribe;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.group.GroupNames;
@@ -62,11 +58,11 @@ import sonia.scm.repository.RepositoryDAO;
import sonia.scm.user.User;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.util.List;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra

View File

@@ -35,12 +35,12 @@ package sonia.scm.security;
import com.google.common.base.Objects;
//~--- JDK imports ------------------------------------------------------------
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
//~--- JDK imports ------------------------------------------------------------
/**
* Secure key can be used for singing messages and tokens.
*

View File

@@ -38,30 +38,23 @@ package sonia.scm.template;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.resources.ResourceManager;
import sonia.scm.resources.ResourceType;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -100,15 +93,12 @@ public class TemplateServlet extends HttpServlet
* @param context
* @param templateEngineFactory
* @param configuration
* @param resourceManager
*/
@Inject
public TemplateServlet(SCMContextProvider context,
TemplateEngineFactory templateEngineFactory,
ResourceManager resourceManager, ScmConfiguration configuration)
TemplateEngineFactory templateEngineFactory, ScmConfiguration configuration)
{
this.templateEngineFactory = templateEngineFactory;
this.resourceManager = resourceManager;
this.configuration = configuration;
this.version = context.getVersion();
}
@@ -123,21 +113,17 @@ public class TemplateServlet extends HttpServlet
* @param response
*
* @throws IOException
* @throws ServletException
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
Map<String, Object> params = new HashMap<String, Object>();
Map<String, Object> params = new HashMap<>();
String contextPath = request.getContextPath();
params.put("contextPath", contextPath);
params.put("configuration", configuration);
params.put("version", version);
params.put("scripts", resourceManager.getResources(ResourceType.SCRIPT));
Locale l = request.getLocale();
if (l == null)
@@ -242,9 +228,6 @@ public class TemplateServlet extends HttpServlet
/** Field description */
private final ScmConfiguration configuration;
/** Field description */
private final ResourceManager resourceManager;
/** Field description */
private final TemplateEngineFactory templateEngineFactory;

View File

@@ -40,9 +40,11 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.AlreadyExistsException;
import sonia.scm.EagerSingleton;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.TransformFilter;
import sonia.scm.search.SearchRequest;
@@ -97,10 +99,7 @@ public class DefaultUserManager extends AbstractUserManager
public DefaultUserManager(UserDAO userDAO)
{
this.userDAO = userDAO;
this.managerDaoAdapter = new ManagerDaoAdapter<>(
userDAO,
UserNotFoundException::new,
UserAlreadyExistsException::new);
this.managerDaoAdapter = new ManagerDaoAdapter<>(userDAO);
}
//~--- methods --------------------------------------------------------------
@@ -139,10 +138,9 @@ public class DefaultUserManager extends AbstractUserManager
* @param user
*
* @throws IOException
* @throws UserException
*/
@Override
public User create(User user) throws UserException {
public User create(User user) throws AlreadyExistsException {
String type = user.getType();
if (Util.isEmpty(type)) {
user.setType(userDAO.getType());
@@ -159,7 +157,7 @@ public class DefaultUserManager extends AbstractUserManager
}
@Override
public void delete(User user) throws UserException {
public void delete(User user) throws NotFoundException {
logger.info("delete user {} of type {}", user.getName(), user.getType());
managerDaoAdapter.delete(
user,
@@ -193,11 +191,9 @@ public class DefaultUserManager extends AbstractUserManager
* @param user
*
* @throws IOException
* @throws UserException
*/
@Override
public void modify(User user) throws UserException
{
public void modify(User user) throws NotFoundException {
logger.info("modify user {} of type {}", user.getName(), user.getType());
managerDaoAdapter.modify(
@@ -214,11 +210,9 @@ public class DefaultUserManager extends AbstractUserManager
* @param user
*
* @throws IOException
* @throws UserException
*/
@Override
public void refresh(User user) throws UserException
{
public void refresh(User user) throws NotFoundException {
if (logger.isInfoEnabled())
{
logger.info("refresh user {} of type {}", user.getName(), user.getType());
@@ -229,7 +223,7 @@ public class DefaultUserManager extends AbstractUserManager
if (fresh == null)
{
throw new UserNotFoundException(user);
throw new NotFoundException();
}
fresh.copyProperties(user);
@@ -455,5 +449,5 @@ public class DefaultUserManager extends AbstractUserManager
//~--- fields ---------------------------------------------------------------
private final UserDAO userDAO;
private final ManagerDaoAdapter<User, UserException> managerDaoAdapter;
private final ManagerDaoAdapter<User> managerDaoAdapter;
}