merge with branch 1.x

This commit is contained in:
Sebastian Sdorra
2014-12-06 15:42:10 +01:00
75 changed files with 5923 additions and 516 deletions

View File

@@ -421,7 +421,7 @@
<properties>
<!-- test libraries -->
<mokito.version>1.9.5</mokito.version>
<mokito.version>1.10.8</mokito.version>
<hamcrest.version>1.3</hamcrest.version>
<junit.version>4.11</junit.version>
@@ -430,8 +430,7 @@
<logback.version>1.1.2</logback.version>
<servlet.version>2.5</servlet.version>
<guice.version>3.0</guice.version>
<jersey.version>1.18.1</jersey.version>
<freemarker.version>2.3.20</freemarker.version>
<jersey.version>1.18.2</jersey.version>
<!-- event bus -->
<legman.version>1.2.0</legman.version>
@@ -445,7 +444,7 @@
<!-- repostitory libraries -->
<jgit.version>3.4.1.201406201815-r</jgit.version>
<svnkit.version>1.8.5-scm2</svnkit.version>
<!-- util libraries -->
<guava.version>16.0.1</guava.version>

View File

@@ -40,7 +40,7 @@
<dependency>
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
<version>2.0.28</version>
<version>2.0.29</version>
</dependency>
<dependency>
@@ -52,7 +52,7 @@
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>${freemarker.version}</version>
<version>2.3.21</version>
</dependency>
</dependencies>

View File

@@ -0,0 +1,186 @@
/**
* 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.cli.cmd;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.Files;
import org.kohsuke.args4j.Option;
import sonia.scm.ConfigurationException;
import sonia.scm.client.ImportBundleRequest;
import sonia.scm.client.ScmClientSession;
import sonia.scm.repository.Repository;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
/**
*
* @author Sebastian Sdorra
* @since 1.43
*/
@Command(
name = "import-from-bundle",
usage = "usageImportBundle",
group = "repository"
)
public class ImportBundleSubCommand extends ImportSubCommand
{
/**
* Method description
*
*
* @return
*/
public File getBundle()
{
return bundle;
}
/**
* Method description
*
*
* @return
*/
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
public boolean isCompressed()
{
return compressed;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param bundle
*/
public void setBundle(File bundle)
{
this.bundle = bundle;
}
/**
* Method description
*
*
* @param compressed
*/
public void setCompressed(boolean compressed)
{
this.compressed = compressed;
}
/**
* Method description
*
*
* @param name
*/
public void setName(String name)
{
this.name = name;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
@Override
protected void run()
{
if (!bundle.exists())
{
throw new ConfigurationException("could not find bundle");
}
else
{
ScmClientSession session = createSession();
ImportBundleRequest req = new ImportBundleRequest(getType(), name,
Files.asByteSource(bundle));
req.setCompressed(compressed);
Repository repository =
session.getRepositoryHandler().importFromBundle(req);
printImportedRepository(repository);
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@Option(
name = "--bundle",
required = true,
usage = "optionRepositoryBundle",
aliases = { "-b" }
)
private File bundle;
/** Field description */
@Option(
name = "--compressed",
usage = "optionRepositoryBundleCompressed",
aliases = { "-c" }
)
private boolean compressed = false;
/** Field description */
@Option(
name = "--name",
required = true,
usage = "optionRepositoryName",
aliases = { "-n" }
)
private String name;
}

View File

@@ -0,0 +1,82 @@
/**
* 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.cli.cmd;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Maps;
import sonia.scm.client.ImportResultWrapper;
import sonia.scm.client.ScmClientSession;
//~--- JDK imports ------------------------------------------------------------
import java.util.Map;
/**
*
* @author Sebastian Sdorra
* @since 1.43
*/
@Command(
name = "import-from-directory",
usage = "usageImportDirectory",
group = "repository"
)
public class ImportDirectorySubCommand extends ImportSubCommand
{
/** Field description */
public static final String TEMPLATE =
"/sonia/resources/import-from-directory.ftl";
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
@Override
protected void run()
{
ScmClientSession session = createSession();
ImportResultWrapper wrapper =
session.getRepositoryHandler().importFromDirectory(getType());
Map<String, Object> env = Maps.newHashMap();
env.put("importedDirectories", wrapper.getImportedDirectories());
env.put("failedDirectories", wrapper.getFailedDirectories());
renderTemplate(env, TEMPLATE);
}
}

View File

@@ -0,0 +1,104 @@
/**
* 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.cli.cmd;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Maps;
import org.kohsuke.args4j.Argument;
import sonia.scm.cli.wrapper.RepositoryWrapper;
import sonia.scm.repository.Repository;
//~--- JDK imports ------------------------------------------------------------
import java.util.Map;
/**
*
* @author Sebastian Sdorra
* @since 1.43
*/
public abstract class ImportSubCommand extends TemplateSubCommand
{
/**
* Method description
*
*
* @return
*/
public String getType()
{
return type;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param type
*/
public void setType(String type)
{
this.type = type;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param repository
*/
protected void printImportedRepository(Repository repository)
{
Map<String, Object> env = Maps.newHashMap();
env.put("repository", new RepositoryWrapper(config, repository));
renderTemplate(env, GetRepositorySubCommand.TEMPLATE);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@Argument(
usage = "optionRepositoryType",
metaVar = "repositorytype",
required = true
)
private String type;
}

View File

@@ -0,0 +1,146 @@
/**
* 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.cli.cmd;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Maps;
import org.kohsuke.args4j.Option;
import sonia.scm.client.ImportUrlRequest;
import sonia.scm.client.ScmClientSession;
import sonia.scm.repository.Repository;
//~--- JDK imports ------------------------------------------------------------
import java.net.URL;
import java.util.Map;
/**
*
* @author Sebastian Sdorra
*/
@Command(
name = "import-from-url",
usage = "usageImportUrl",
group = "repository"
)
public class ImportUrlSubCommand extends ImportSubCommand
{
/**
* Method description
*
*
* @return
*/
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
public URL getUrl()
{
return url;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param name
*/
public void setName(String name)
{
this.name = name;
}
/**
* Method description
*
*
* @param url
*/
public void setUrl(URL url)
{
this.url = url;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
@Override
protected void run()
{
ScmClientSession session = createSession();
ImportUrlRequest request = new ImportUrlRequest(getType(), name,
url.toExternalForm());
Repository repository =
session.getRepositoryHandler().importFromUrl(request);
printImportedRepository(repository);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@Option(
name = "--name",
required = true,
usage = "optionRepositoryName",
aliases = { "-n" }
)
private String name;
/** Field description */
@Option(
name = "--url",
required = true,
usage = "optionRemoteRepositoryUrl",
aliases = { "-r" }
)
private URL url;
}

View File

@@ -37,6 +37,9 @@ sonia.scm.cli.cmd.ModifyRepositorySubCommand
sonia.scm.cli.cmd.GetRepositorySubCommand
sonia.scm.cli.cmd.ListRepositoriesSubCommand
sonia.scm.cli.cmd.DeleteRepositorySubCommand
sonia.scm.cli.cmd.ImportDirectorySubCommand
sonia.scm.cli.cmd.ImportUrlSubCommand
sonia.scm.cli.cmd.ImportBundleSubCommand
# permission
sonia.scm.cli.cmd.AddPermissionSubCommand

View File

@@ -46,11 +46,14 @@ optionTemplateFile = Template file
optionRepositoryId = Repository Id
optionRepositoryIdOrTypeAndName = Repository Id or type/name
optionRepositoryName = Repository name
optionRepositoryType = Repository name
optionRepositoryType = Repository type
optionRepositoryContact = Repository contact
optionRepositoryDescription = Repository description
optionRepositoryPublic = Repository public readable
optionRepositoryArchive = Repository archived
optionRemoteRepositoryUrl = Remote repository url
optionRepositoryBundle = Import repository from a bundle file (e.g. svn dump)
optionRepositoryBundleCompressed = Indicates that the bundle is gzip compressed
optionPermissionGroup = Group
optionPermissionName = Group or user name
@@ -81,6 +84,7 @@ permissiontype = value
groupname = groupname
repositoryid = repositoryid
username = username
repositorytype = type
config = Configuration
misc = Miscellaneous
@@ -90,6 +94,8 @@ user = User
security = Security
level = Logging-Level
boolean = true or false
URL = url
bundle = file
options = Options
usage = scm-cli-client [options] command [command options]
@@ -118,6 +124,8 @@ usageModifyRepository = Modify a repository
usageStoreConfig = Stores the current configuration
usageVersion = Show the version of scm-cli-client
usageServerVersion = Show the version of the scm-manager
usageImportDirectory = Import repositories from repository directory
usageImportUrl = Import repository from remote url
usageEncrypt = Encrypts the given value
usageGenerateKey = Generates a unique key

View File

@@ -0,0 +1,9 @@
Imported repositories:
<#list importedDirectories as imported>
- ${imported}
</#list>
Repositories failed to import:
<#list failedDirectories as failed>
- ${failed}
</#list>

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.client;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.ByteSource;
/**
*
* @author Sebastian Sdorra
* @since 1.43
*/
public class ImportBundleRequest
{
/**
* Constructs ...
*
*/
ImportBundleRequest() {}
/**
* Constructs ...
*
*
* @param type
* @param name
* @param bundle
*/
public ImportBundleRequest(String type, String name, ByteSource bundle)
{
this.type = type;
this.name = name;
this.bundle = bundle;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public ByteSource getBundle()
{
return bundle;
}
/**
* Method description
*
*
* @return
*/
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
public String getType()
{
return type;
}
/**
* Method description
*
*
* @return
*/
public boolean isCompressed()
{
return compressed;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param compressed
*/
public void setCompressed(boolean compressed)
{
this.compressed = compressed;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ByteSource bundle;
/** Field description */
private boolean compressed = false;
/** Field description */
private String name;
/** Field description */
private String type;
}

View File

@@ -0,0 +1,184 @@
/**
* 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.client;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import sonia.scm.repository.ImportResult;
import sonia.scm.repository.Repository;
//~--- JDK imports ------------------------------------------------------------
import java.util.List;
/**
*
* @author Sebastian Sdorra
* @since 1.43
*/
public class ImportResultWrapper
{
/**
* Constructs ...
*
*
* @param client
* @param type
* @param result
*/
public ImportResultWrapper(RepositoryClientHandler client, String type,
ImportResult result)
{
this.client = client;
this.type = type;
this.result = result;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public List<String> getFailedDirectories()
{
List<String> directories = result.getFailedDirectories();
if (directories == null)
{
directories = ImmutableList.of();
}
return directories;
}
/**
* Method description
*
*
* @return
*/
public List<String> getImportedDirectories()
{
List<String> directories = result.getImportedDirectories();
if (directories == null)
{
directories = ImmutableList.of();
}
return directories;
}
/**
* Method description
*
*
* @return
*/
public Iterable<Repository> getImportedRepositories()
{
return Iterables.transform(getImportedDirectories(),
new RepositoryResolver(client, type));
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 14/11/29
* @author Enter your name here...
*/
private static class RepositoryResolver
implements Function<String, Repository>
{
/**
* Constructs ...
*
*
* @param clientHandler
* @param type
*/
public RepositoryResolver(RepositoryClientHandler clientHandler,
String type)
{
this.clientHandler = clientHandler;
this.type = type;
}
//~--- methods ------------------------------------------------------------
/**
* Method description
*
*
* @param name
*
* @return
*/
@Override
public Repository apply(String name)
{
return clientHandler.get(type, type);
}
//~--- fields -------------------------------------------------------------
/** Field description */
private final RepositoryClientHandler clientHandler;
/** Field description */
private final String type;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final RepositoryClientHandler client;
/** Field description */
private final ImportResult result;
/** Field description */
private final String type;
}

View File

@@ -0,0 +1,118 @@
/**
* 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.client;
//~--- JDK imports ------------------------------------------------------------
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
*
* @author Sebastian Sdorra
* @since 1.43
*/
@XmlRootElement(name = "import")
@XmlAccessorType(XmlAccessType.FIELD)
public class ImportUrlRequest
{
/**
* Constructs ...
*
*/
ImportUrlRequest() {}
/**
* Constructs ...
*
*
* @param type
* @param name
* @param url
*/
public ImportUrlRequest(String type, String name, String url)
{
this.type = type;
this.name = name;
this.url = url;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
public String getType()
{
return type;
}
/**
* Method description
*
*
* @return
*/
public String getUrl()
{
return url;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String name;
/** Field description */
@XmlTransient
private String type;
/** Field description */
private String url;
}

View File

@@ -51,6 +51,43 @@ import java.util.Collection;
public interface RepositoryClientHandler extends ClientHandler<Repository>
{
/**
* Method description
*
*
* @param type
*
* @return
*
* @since 1.43
*/
public ImportResultWrapper importFromDirectory(String type);
/**
* Method description
*
*
* @param request
*
* @return
*
* @since 1.43
*/
public Repository importFromBundle(ImportBundleRequest request);
/**
* Method description
*
*
* @param request
* @return
*
* @since 1.43
*/
public Repository importFromUrl(ImportUrlRequest request);
//~--- get methods ----------------------------------------------------------
/**
* Method description
*

View File

@@ -45,6 +45,12 @@
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- test scope -->
<dependency>

View File

@@ -51,8 +51,11 @@ import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.ClientFilter;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.sun.jersey.multipart.impl.MultiPartWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -122,7 +125,8 @@ public class JerseyClientProvider implements ScmClientProvider
UrlProvider urlProvider = UrlProviderFactory.createUrlProvider(url,
UrlProviderFactory.TYPE_RESTAPI_XML);
Client client = Client.create();
Client client =
Client.create(new DefaultClientConfig(MultiPartWriter.class));
client.addFilter(new CookieClientFilter());

View File

@@ -35,20 +35,32 @@ package sonia.scm.client;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
import sonia.scm.NotSupportedFeatuerException;
import sonia.scm.Type;
import sonia.scm.repository.ImportResult;
import sonia.scm.repository.Repository;
import sonia.scm.repository.Tags;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.multipart.FormDataMultiPart;
import com.sun.jersey.multipart.file.StreamDataBodyPart;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import javax.ws.rs.core.MediaType;
/**
*
* @author Sebastian Sdorra
@@ -57,6 +69,29 @@ public class JerseyRepositoryClientHandler
extends AbstractClientHandler<Repository> implements RepositoryClientHandler
{
/** Field description */
private static final String IMPORT_TYPE_BUNDLE = "bundle";
/** Field description */
private static final String IMPORT_TYPE_DIRECTORY = "directory";
/** Field description */
private static final String IMPORT_TYPE_URL = "url";
/** Field description */
private static final String PARAM_BUNDLE = "bundle";
/** Field description */
private static final String PARAM_COMPRESSED = "compressed";
/** Field description */
private static final String PARAM_NAME = "name";
/** Field description */
private static final String URL_IMPORT = "import/repositories/";
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
@@ -68,6 +103,108 @@ public class JerseyRepositoryClientHandler
super(session, Repository.class);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
*
* @return
*/
@Override
public Repository importFromBundle(ImportBundleRequest request)
{
WebResource r = client.resource(getImportUrl(request.getType(),
IMPORT_TYPE_BUNDLE)).queryParam(PARAM_COMPRESSED,
Boolean.toString(request.isCompressed()));
Repository repository = null;
InputStream stream = null;
try
{
stream = request.getBundle().openStream();
FormDataMultiPart form = new FormDataMultiPart();
form.field(PARAM_NAME, request.getName());
form.bodyPart(new StreamDataBodyPart(PARAM_BUNDLE, stream));
ClientResponse response =
r.type(MediaType.MULTIPART_FORM_DATA).post(ClientResponse.class, form);
ClientUtil.checkResponse(response);
String location =
response.getHeaders().getFirst(HttpUtil.HEADER_LOCATION);
if (Strings.isNullOrEmpty(location))
{
throw new ScmClientException("no location header found after import");
}
repository = getItemByUrl(location);
}
catch (IOException ex)
{
throw new ScmClientException("could not import bundle", ex);
}
finally
{
IOUtil.close(stream);
}
return repository;
}
/**
* Method description
*
*
* @param type
*
* @return
*/
@Override
public ImportResultWrapper importFromDirectory(String type)
{
WebResource r = client.resource(getImportUrl(type, IMPORT_TYPE_DIRECTORY));
ClientResponse response = r.post(ClientResponse.class);
ClientUtil.checkResponse(response);
return new ImportResultWrapper(this, type,
response.getEntity(ImportResult.class));
}
/**
* Method description
*
*
* @param request
*
* @return
*/
@Override
public Repository importFromUrl(ImportUrlRequest request)
{
WebResource r = client.resource(getImportUrl(request.getType(),
IMPORT_TYPE_URL));
ClientResponse response = r.post(ClientResponse.class, request);
ClientUtil.checkResponse(response);
String location = response.getHeaders().getFirst(HttpUtil.HEADER_LOCATION);
if (Strings.isNullOrEmpty(location))
{
throw new ScmClientException("no location header found after import");
}
return getItemByUrl(location);
}
//~--- get methods ----------------------------------------------------------
/**
@@ -228,4 +365,22 @@ public class JerseyRepositoryClientHandler
{
return urlProvider.getRepositoryUrlProvider().getAllUrl();
}
/**
* Method description
*
*
* @param type
* @param importType
*
* @return
*/
private String getImportUrl(String type, String importType)
{
StringBuilder buffer = new StringBuilder(URL_IMPORT);
buffer.append(type).append(HttpUtil.SEPARATOR_PATH).append(importType);
return HttpUtil.append(urlProvider.getBaseUrl(), buffer.toString());
}
}

View File

@@ -35,23 +35,28 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Throwables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.ImportResult.Builder;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Abstract base class for directory based {@link ImportHandler} and
* {@link AdvancedImportHandler}.
*
* @author Sebastian Sdorra
* @since 1.12
*/
public abstract class AbstactImportHandler implements ImportHandler
public abstract class AbstactImportHandler implements AdvancedImportHandler
{
/**
@@ -63,80 +68,65 @@ public abstract class AbstactImportHandler implements ImportHandler
//~--- get methods ----------------------------------------------------------
/**
* Method description
* Returns array of repository directory names.
*
*
* @return
* @return repository directory names
*/
protected abstract String[] getDirectoryNames();
/**
* Method description
* Returns repository handler.
*
*
* @return
* @return repository handler
*/
protected abstract AbstractRepositoryHandler<?> getRepositoryHandler();
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param manager
*
*
* @return
* @throws IOException
* @throws RepositoryException
* {@inheritDoc}
*/
@Override
public List<String> importRepositories(RepositoryManager manager)
throws IOException, RepositoryException
{
List<String> imported = new ArrayList<String>();
if (logger.isTraceEnabled())
{
logger.trace("search for repositories to import");
}
List<String> repositoryNames =
RepositoryUtil.getRepositoryNames(getRepositoryHandler(),
getDirectoryNames());
for (String repositoryName : repositoryNames)
{
if (logger.isTraceEnabled())
{
logger.trace("check repository {} for import", repositoryName);
}
Repository repository = manager.get(getTypeName(), repositoryName);
if (repository == null)
{
importRepository(manager, repositoryName);
imported.add(repositoryName);
}
else if (logger.isDebugEnabled())
{
logger.debug("repository {} is allready managed", repositoryName);
}
}
return imported;
return doRepositoryImport(manager, true).getImportedDirectories();
}
/**
* Method description
* {@inheritDoc}
*/
@Override
public ImportResult importRepositoriesFromDirectory(RepositoryManager manager)
{
try
{
return doRepositoryImport(manager, false);
}
catch (IOException ex)
{
// should never happen
throw Throwables.propagate(ex);
}
catch (RepositoryException ex)
{
// should never happen
throw Throwables.propagate(ex);
}
}
/**
* Creates a repository.
*
*
* @param repositoryDirectory
* @param repositoryName
* @param repositoryDirectory repository base directory
* @param repositoryName name of the repository
*
* @return
* @return repository
*
* @throws IOException
* @throws RepositoryException
@@ -154,6 +144,118 @@ public abstract class AbstactImportHandler implements ImportHandler
return repository;
}
/**
* Repository import.
*
*
* @param manager repository manager
* @param throwExceptions true to throw exception
*
* @return import result
*
* @throws IOException
* @throws RepositoryException
*/
private ImportResult doRepositoryImport(RepositoryManager manager,
boolean throwExceptions)
throws IOException, RepositoryException
{
Builder builder = ImportResult.builder();
logger.trace("search for repositories to import");
try
{
List<String> repositoryNames =
RepositoryUtil.getRepositoryNames(getRepositoryHandler(),
getDirectoryNames());
for (String repositoryName : repositoryNames)
{
importRepository(manager, builder, throwExceptions, repositoryName);
}
}
catch (IOException ex)
{
handleException(ex, throwExceptions);
}
return builder.build();
}
/**
* Method description
*
*
* @param ex
* @param throwExceptions
* @param <T>
*
* @throws T
*/
private <T extends Exception> void handleException(T ex,
boolean throwExceptions)
throws T
{
logger.warn("error durring repository directory import", ex);
if (throwExceptions)
{
throw ex;
}
}
/**
* Method description
*
*
* @param manager
* @param builder
* @param throwExceptions
* @param repositoryName
*
* @throws IOException
* @throws RepositoryException
*/
private void importRepository(RepositoryManager manager, Builder builder,
boolean throwExceptions, String repositoryName)
throws IOException, RepositoryException
{
logger.trace("check repository {} for import", repositoryName);
Repository repository = manager.get(getTypeName(), repositoryName);
if (repository == null)
{
try
{
importRepository(manager, repositoryName);
builder.addImportedDirectory(repositoryName);
}
catch (IOException ex)
{
builder.addFailedDirectory(repositoryName);
handleException(ex, throwExceptions);
}
catch (IllegalStateException ex)
{
builder.addFailedDirectory(repositoryName);
handleException(ex, throwExceptions);
}
catch (RepositoryException ex)
{
builder.addFailedDirectory(repositoryName);
handleException(ex, throwExceptions);
}
}
else if (logger.isDebugEnabled())
{
logger.debug("repository {} is allready managed", repositoryName);
}
}
/**
* Method description
*

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository;
/**
* Searches and import existing repositories. The {@link AdvancedImportHandler}
* gives more control over the result of the import as the
* {@link ImportHandler}.
*
* @author Sebastian Sdorra
* @since 1.43
*/
public interface AdvancedImportHandler extends ImportHandler
{
/**
* Import existing and non managed repositories. Returns result which
* contains names of the successfully imported directories and the names of
* the failed directories
*
*
* @param manager The global {@link RepositoryManager}
*
* @return result which contains names of the successfully imported
* directories and the names of the failed directories.
*/
public ImportResult importRepositoriesFromDirectory(
RepositoryManager manager);
}

View File

@@ -0,0 +1,239 @@
/**
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Import result of the {@link AdvancedImportHandler}.
*
* @author Sebastian Sdorra
* @since 1.43
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "import-result")
public final class ImportResult
{
/**
* Constructs ...
*
*/
ImportResult() {}
/**
* Constructs a new import result.
*
*
* @param importedDirectories imported directories
* @param failedDirectories failed directories
*/
public ImportResult(List<String> importedDirectories,
List<String> failedDirectories)
{
this.importedDirectories = checkNotNull(importedDirectories,
"list of imported directories is required");
this.failedDirectories = checkNotNull(failedDirectories,
"list of failed directories is required");
}
//~--- methods --------------------------------------------------------------
/**
* Returns a import result builder.
*
*
* @return import result builder
*/
public static Builder builder()
{
return new Builder();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final ImportResult other = (ImportResult) obj;
return Objects.equal(importedDirectories, other.importedDirectories)
&& Objects.equal(failedDirectories, other.failedDirectories);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return Objects.hashCode(importedDirectories, failedDirectories);
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
//J-
return Objects.toStringHelper(this)
.add("importedDirectories", importedDirectories)
.add("failedDirectories", failedDirectories)
.toString();
//J+
}
//~--- get methods ----------------------------------------------------------
/**
* Returns list of failed directories.
*
*
* @return list of failed directories
*/
public List<String> getFailedDirectories()
{
return failedDirectories;
}
/**
* Returns list of successfully imported directories.
*
*
* @return list of successfully imported directories
*/
public List<String> getImportedDirectories()
{
return importedDirectories;
}
//~--- inner classes --------------------------------------------------------
/**
* Builder for {@link ImportResult}.
*/
public static class Builder
{
/**
* Constructs ...
*
*/
private Builder() {}
//~--- methods ------------------------------------------------------------
/**
* Adds a failed directory to the import result.
*
*
* @param name name of the directory
*
* @return {@code this}
*/
public Builder addFailedDirectory(String name)
{
this.failedDirectories.add(name);
return this;
}
/**
* Adds a successfully imported directory to the import result.
*
*
* @param name name of the directory
*
* @return {@code this}
*/
public Builder addImportedDirectory(String name)
{
this.importedDirectories.add(name);
return this;
}
/**
* Builds the final import result.
*
*
* @return final import result
*/
public ImportResult build()
{
return new ImportResult(ImmutableList.copyOf(importedDirectories),
ImmutableList.copyOf(failedDirectories));
}
//~--- fields -------------------------------------------------------------
/** successfully imported directories */
private final List<String> importedDirectories = Lists.newArrayList();
/** failed directories */
private final List<String> failedDirectories = Lists.newArrayList();
}
//~--- fields ---------------------------------------------------------------
/** failed directories */
private List<String> failedDirectories;
/** successfully imported directories */
private List<String> importedDirectories;
}

View File

@@ -0,0 +1,121 @@
/**
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository.api;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
/**
* Abstract class for bundle or unbundle command.
*
* @author Sebastian Sdorra
* @since 1.43
*/
public abstract class AbstractBundleOrUnbundleCommandResponse
{
/**
* Constructs a new bundle/unbundle response.
*
*
* @param changesetCount count of bundled/unbundled changesets
*/
protected AbstractBundleOrUnbundleCommandResponse(long changesetCount)
{
this.changesetCount = changesetCount;
}
//~--- methods --------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final AbstractBundleOrUnbundleCommandResponse other =
(AbstractBundleOrUnbundleCommandResponse) obj;
return Objects.equal(changesetCount, other.changesetCount);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return Objects.hashCode(changesetCount);
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
//J-
return Objects.toStringHelper(this)
.add("changesetCount", changesetCount)
.toString();
//J+
}
//~--- get methods ----------------------------------------------------------
/**
* Returns the count of bundled/unbundled changesets.
*
*
* @return count of bundled/unbundled changesets
*/
public long getChangesetCount()
{
return changesetCount;
}
//~--- fields ---------------------------------------------------------------
/** count of bundled/unbundled changesets */
private final long changesetCount;
}

View File

@@ -0,0 +1,185 @@
/**
* 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.repository.api;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.ByteSink;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.google.common.io.OutputSupplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.spi.BundleCommand;
import sonia.scm.repository.spi.BundleCommandRequest;
import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
/**
* The bundle command dumps a repository to a byte source such as a file. The
* created bundle can be restored to an empty repository with the
* {@link UnbundleCommandBuilder}.
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
* @since 1.43
*/
public final class BundleCommandBuilder
{
/** logger for BundleCommandBuilder */
private static final Logger logger =
LoggerFactory.getLogger(BundleCommandBuilder.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs a new {@link BundleCommandBuilder}.
*
*
* @param bundleCommand bundle command implementation
* @param repository repository
*/
BundleCommandBuilder(BundleCommand bundleCommand, Repository repository)
{
this.bundleCommand = bundleCommand;
this.repository = repository;
}
//~--- methods --------------------------------------------------------------
/**
* Dumps the repository to the given {@link File}.
*
* @param outputFile output file
*
* @return bundle response
*
* @throws IOException
* @throws RepositoryException
*/
public BundleResponse bundle(File outputFile)
throws IOException, RepositoryException
{
checkArgument((outputFile != null) &&!outputFile.exists(),
"file is null or exists already");
BundleCommandRequest request =
new BundleCommandRequest(Files.asByteSink(outputFile));
logger.info("create bundle at {} for repository {}", outputFile,
repository.getId());
return bundleCommand.bundle(request);
}
/**
* Dumps the repository to the given {@link OutputStream}.
*
*
* @param outputStream output stream
*
* @return bundle response
*
* @throws IOException
* @throws RepositoryException
*/
public BundleResponse bundle(OutputStream outputStream)
throws IOException, RepositoryException
{
checkNotNull(outputStream, "output stream is required");
logger.info("bundle {} to output stream", repository.getId());
return bundleCommand.bundle(
new BundleCommandRequest(asByteSink(outputStream)));
}
/**
* Dumps the repository to the given {@link ByteSink}.
*
* @param sink byte sink
*
* @return bundle response
*
* @throws IOException
* @throws RepositoryException
*/
public BundleResponse bundle(ByteSink sink)
throws IOException, RepositoryException
{
checkNotNull(sink, "byte sink is required");
logger.info("bundle {} to byte sink");
return bundleCommand.bundle(new BundleCommandRequest(sink));
}
/**
* Converts an {@link OutputStream} into a {@link ByteSink}.
*
*
* @param outputStream ouput stream to convert
*
* @return converted byte sink
*/
private ByteSink asByteSink(final OutputStream outputStream)
{
return ByteStreams.asByteSink(new OutputSupplier<OutputStream>()
{
@Override
public OutputStream getOutput() throws IOException
{
return outputStream;
}
});
}
//~--- fields ---------------------------------------------------------------
/** bundle command implementation */
private final BundleCommand bundleCommand;
/** repository */
private final Repository repository;
}

View File

@@ -0,0 +1,56 @@
/**
* 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.repository.api;
/**
* Response of bundle command.
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
* @since 1.43
*/
public final class BundleResponse
extends AbstractBundleOrUnbundleCommandResponse
{
/**
* Constructs a new bundle response.
*
*
* @param changesetCount count of bundled changesets
*/
public BundleResponse(long changesetCount)
{
super(changesetCount);
}
}

View File

@@ -56,5 +56,10 @@ public enum Command
/**
* @since 1.31
*/
INCOMING, OUTGOING, PUSH, PULL;
INCOMING, OUTGOING, PUSH, PULL,
/**
* @since 1.43
*/
BUNDLE, UNBUNDLE;
}

View File

@@ -50,6 +50,7 @@ import sonia.scm.security.RepositoryPermission;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.net.URL;
/**
* The pull command pull changes from a other repository.
@@ -83,6 +84,38 @@ public final class PullCommandBuilder
//~--- methods --------------------------------------------------------------
/**
* Pull all changes from the given remote url.
*
*
* @param url remote url
*
* @return informations over the executed pull command
*
* @throws IOException
* @throws RepositoryException
*
* @since 1.43
*/
public PullResponse pull(String url)
throws IOException, RepositoryException
{
Subject subject = SecurityUtils.getSubject();
//J-
subject.checkPermission(
new RepositoryPermission(localRepository, PermissionType.WRITE)
);
//J+
URL remoteUrl = new URL(url);
request.reset();
request.setRemoteUrl(remoteUrl);
logger.info("pull changes from url {}", url);
return command.pull(request);
}
/**
* Pull all changes from the given remote repository.
*
@@ -108,6 +141,7 @@ public final class PullCommandBuilder
);
//J+
request.reset();
request.setRemoteRepository(remoteRepository);
logger.info("pull changes from {}", remoteRepository);

View File

@@ -30,6 +30,7 @@
*/
package sonia.scm.repository.api;
//~--- non-JDK imports --------------------------------------------------------
@@ -37,6 +38,9 @@ package sonia.scm.repository.api;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
@@ -48,15 +52,25 @@ import sonia.scm.security.RepositoryPermission;
import java.io.IOException;
import java.net.URL;
/**
* The push command push changes to a other repository.
*
*
* @author Sebastian Sdorra
* @since 1.31
*/
public final class PushCommandBuilder
{
/**
* the logger for PushCommandBuilder
*/
private static final Logger logger =
LoggerFactory.getLogger(PushCommandBuilder.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs a new PushCommandBuilder.
*
@@ -90,11 +104,39 @@ public final class PushCommandBuilder
);
//J+
logger.info("push changes to repository {}", remoteRepository.getId());
request.reset();
request.setRemoteRepository(remoteRepository);
return command.push(request);
}
/**
* Push all changes to the given remote url.
*
* @param url url of a remote repository
*
* @return informations of the executed push command
*
* @throws IOException
* @throws RepositoryException
*
* @since 1.43
*/
public PushResponse push(String url) throws IOException, RepositoryException
{
URL remoteUrl = new URL(url);
logger.info("push changes to url {}", url);
request.reset();
request.setRemoteUrl(remoteUrl);
return command.push(request);
}
//~--- fields ---------------------------------------------------------------
/** push command implementation */

View File

@@ -83,6 +83,8 @@ import java.io.IOException;
* @apiviz.uses sonia.scm.repository.api.OutgoingCommandBuilder
* @apiviz.uses sonia.scm.repository.api.PullCommandBuilder
* @apiviz.uses sonia.scm.repository.api.PushCommandBuilder
* @apiviz.uses sonia.scm.repository.api.BundleCommandBuilder
* @apiviz.uses sonia.scm.repository.api.UnbundleCommandBuilder
*/
public final class RepositoryService implements Closeable
{
@@ -204,6 +206,25 @@ public final class RepositoryService implements Closeable
repository, preProcessorUtil);
}
/**
* The bundle command creates an archive from the repository.
*
* @return instance of {@link BundleCommandBuilder}
* @throws CommandNotSupportedException if the command is not supported
* by the implementation of the repository service provider.
* @since 1.43
*/
public BundleCommandBuilder getBundleCommand()
{
if (logger.isDebugEnabled())
{
logger.debug("create bundle command for repository {}",
repository.getName());
}
return new BundleCommandBuilder(provider.getBundleCommand(), repository);
}
/**
* The cat command show the content of a given file.
*
@@ -370,6 +391,26 @@ public final class RepositoryService implements Closeable
repository);
}
/**
* The unbundle command restores a repository from the given bundle.
*
* @return instance of {@link UnbundleCommandBuilder}
* @throws CommandNotSupportedException if the command is not supported
* by the implementation of the repository service provider.
* @since 1.43
*/
public UnbundleCommandBuilder getUnbundleCommand()
{
if (logger.isDebugEnabled())
{
logger.debug("create bundle command for repository {}",
repository.getName());
}
return new UnbundleCommandBuilder(provider.getUnbundleCommand(),
repository);
}
/**
* Returns true if the command is supported by the repository service.
*
@@ -390,7 +431,7 @@ public final class RepositoryService implements Closeable
* @param feature feature
*
* @return true if the feature is supported
*
*
* @since 1.25
*/
public boolean isSupported(Feature feature)

View File

@@ -0,0 +1,275 @@
/**
* 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.repository.api;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.google.common.io.InputSupplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.spi.UnbundleCommand;
import sonia.scm.repository.spi.UnbundleCommandRequest;
import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
/**
* The unbundle command can restore an empty repository from a bundle. The
* bundle can be created with the {@link BundleCommandBuilder}.
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
* @since 1.43
*/
public final class UnbundleCommandBuilder
{
/** logger for UnbundleCommandBuilder */
private static final Logger logger =
LoggerFactory.getLogger(UnbundleCommandBuilder.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs a new UnbundleCommandBuilder.
*
*
* @param unbundleCommand unbundle command implementation
* @param repository repository
*/
public UnbundleCommandBuilder(UnbundleCommand unbundleCommand,
Repository repository)
{
this.unbundleCommand = unbundleCommand;
this.repository = repository;
}
//~--- methods --------------------------------------------------------------
/**
* Restores the repository from the given bundle.
*
*
* @param inputFile input file
*
* @return unbundle response
*
* @throws IOException
* @throws RepositoryException
*/
public UnbundleResponse unbundle(File inputFile)
throws IOException, RepositoryException
{
checkArgument((inputFile != null) && inputFile.exists(),
"existing file is required");
UnbundleCommandRequest request =
createRequest(Files.asByteSource(inputFile));
logger.info("unbundle archive {} at {}", inputFile, repository.getId());
return unbundleCommand.unbundle(request);
}
/**
* Restores the repository from the given bundle.
*
*
* @param inputStream input stream
*
* @return unbundle response
*
* @throws IOException
* @throws RepositoryException
*/
public UnbundleResponse unbundle(InputStream inputStream)
throws IOException, RepositoryException
{
checkNotNull(inputStream, "input stream is required");
logger.info("unbundle archive from stream");
return unbundleCommand.unbundle(createRequest(asByteSource(inputStream)));
}
/**
* Restores the repository from the given bundle.
*
*
* @param byteSource byte source
*
* @return unbundle response
*
* @throws IOException
* @throws RepositoryException
*/
public UnbundleResponse unbundle(ByteSource byteSource)
throws IOException, RepositoryException
{
checkNotNull(byteSource, "byte source is required");
logger.info("unbundle from byte source");
return unbundleCommand.unbundle(createRequest(byteSource));
}
//~--- set methods ----------------------------------------------------------
/**
* Set to {@code true} if bundle is gzip compressed. Default is {@code false}.
*
*
* @param compressed {@code true} if bundle is gzip compressed
*
* @return {@code this}
*/
public UnbundleCommandBuilder setCompressed(boolean compressed)
{
this.compressed = compressed;
return this;
}
//~--- methods --------------------------------------------------------------
/**
* Converts an {@link InputStream} into a {@link ByteSource}.
*
*
* @param inputStream input stream
*
* @return byte source
*/
private ByteSource asByteSource(final InputStream inputStream)
{
return ByteStreams.asByteSource(new InputSupplier<InputStream>()
{
@Override
public InputStream getInput() throws IOException
{
return inputStream;
}
});
}
/**
* Creates the {@link UnbundleCommandRequest}.
*
*
* @param source byte source
*
* @return the create request
*/
private UnbundleCommandRequest createRequest(ByteSource source)
{
ByteSource bs;
if (compressed)
{
logger.debug("decode gzip stream for unbundle command");
bs = new CompressedByteSource(source);
}
else
{
bs = source;
}
return new UnbundleCommandRequest(bs);
}
//~--- inner classes --------------------------------------------------------
/**
* ByteSource which is able to handle gzip compressed resources.
*/
private static class CompressedByteSource extends ByteSource
{
/**
* Constructs ...
*
*
* @param wrapped
*/
public CompressedByteSource(ByteSource wrapped)
{
this.wrapped = wrapped;
}
//~--- methods ------------------------------------------------------------
/**
* Opens the stream for reading the compressed source.
*
*
* @return input stream
*
* @throws IOException
*/
@Override
public InputStream openStream() throws IOException
{
return new GZIPInputStream(wrapped.openStream());
}
//~--- fields -------------------------------------------------------------
/** Field description */
private final ByteSource wrapped;
}
//~--- fields ---------------------------------------------------------------
/** repository */
private final Repository repository;
/** unbundle command implementation */
private final UnbundleCommand unbundleCommand;
/** Field description */
private boolean compressed = false;
}

View File

@@ -0,0 +1,55 @@
/**
* 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.repository.api;
/**
* Response of unbundle command.
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
* @since 1.43
*/
public class UnbundleResponse extends AbstractBundleOrUnbundleCommandResponse
{
/**
* Constructs a new unbundle response.
*
*
* @param changesetCount count of unbundled changesets
*/
public UnbundleResponse(long changesetCount)
{
super(changesetCount);
}
}

View File

@@ -0,0 +1,67 @@
/**
* 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.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.api.BundleResponse;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
/**
* Service provider implementation for the bundle command.
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
* @since 1.43
*/
public interface BundleCommand
{
/**
* The bundle command dumps a repository to a byte source such as a file.
*
*
* @param request bundle command request
*
* @return bundle response
*
* @throws IOException
* @throws RepositoryException
*/
public BundleResponse bundle(BundleCommandRequest request)
throws IOException, RepositoryException;
}

View File

@@ -0,0 +1,110 @@
/**
* 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.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.common.io.ByteSink;
/**
* Request for the bundle command.
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
* @since 1.43
*/
public final class BundleCommandRequest
{
/**
* Constructs a new bundle command request.
*
*
* @param archive byte sink archive
*/
public BundleCommandRequest(ByteSink archive)
{
this.archive = archive;
}
//~--- methods --------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final BundleCommandRequest other = (BundleCommandRequest) obj;
return Objects.equal(archive, other.archive);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return Objects.hashCode(archive);
}
//~--- get methods ----------------------------------------------------------
/**
* Returns the archive as {@link ByteSink}.
*
*
* @return {@link ByteSink} archive.
*/
ByteSink getArchive()
{
return archive;
}
//~--- fields ---------------------------------------------------------------
/** byte sink archive */
private final ByteSink archive;
}

View File

@@ -33,7 +33,8 @@
package sonia.scm.repository.spi;
/**
*
* Request object for {@link PullCommand}.
*
* @author Sebastian Sdorra
* @since 1.31
*/

View File

@@ -33,6 +33,7 @@
package sonia.scm.repository.spi;
/**
* Request object for {@link PushCommand}.
*
* @author Sebastian Sdorra
* @since 1.31

View File

@@ -30,6 +30,7 @@
*/
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
@@ -38,12 +39,16 @@ import com.google.common.base.Objects;
import sonia.scm.repository.Repository;
//~--- JDK imports ------------------------------------------------------------
import java.net.URL;
/**
*
* @author Sebastian Sdorra
* @since 1.31
*/
public abstract class RemoteCommandRequest
public abstract class RemoteCommandRequest implements Resetable
{
/**
@@ -64,7 +69,8 @@ public abstract class RemoteCommandRequest
final RemoteCommandRequest other = (RemoteCommandRequest) obj;
return Objects.equal(remoteRepository, other.remoteRepository);
return Objects.equal(remoteRepository, other.remoteRepository)
&& Objects.equal(remoteUrl, other.remoteUrl);
}
/**
@@ -73,7 +79,19 @@ public abstract class RemoteCommandRequest
@Override
public int hashCode()
{
return Objects.hashCode(remoteRepository);
return Objects.hashCode(remoteRepository, remoteUrl);
}
/**
* Resets the request object.
*
* @since 1.43
*/
@Override
public void reset()
{
remoteRepository = null;
remoteUrl = null;
}
/**
@@ -82,10 +100,10 @@ public abstract class RemoteCommandRequest
@Override
public String toString()
{
//J-
return Objects.toStringHelper(this)
.add("remoteRepository", remoteRepository)
.add("remoteUrl", remoteUrl)
.toString();
//J+
}
@@ -102,6 +120,19 @@ public abstract class RemoteCommandRequest
this.remoteRepository = remoteRepository;
}
/**
* Method description
*
*
* @param remoteUrl
*
* @since 1.43
*/
public void setRemoteUrl(URL remoteUrl)
{
this.remoteUrl = remoteUrl;
}
//~--- get methods ----------------------------------------------------------
/**
@@ -115,8 +146,24 @@ public abstract class RemoteCommandRequest
return remoteRepository;
}
/**
* Method description
*
*
* @return
*
* @since 1.43
*/
URL getRemoteUrl()
{
return remoteUrl;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
protected Repository remoteRepository;
/** remote url */
protected URL remoteUrl;
}

View File

@@ -115,6 +115,19 @@ public abstract class RepositoryServiceProvider implements Closeable
throw new CommandNotSupportedException(Command.BROWSE);
}
/**
* Method description
*
*
* @return
*
* @since 1.43
*/
public BundleCommand getBundleCommand()
{
throw new CommandNotSupportedException(Command.BUNDLE);
}
/**
* Method description
*
@@ -218,4 +231,17 @@ public abstract class RepositoryServiceProvider implements Closeable
{
throw new CommandNotSupportedException(Command.TAGS);
}
/**
* Method description
*
*
* @return
*
* @since 1.43
*/
public UnbundleCommand getUnbundleCommand()
{
throw new CommandNotSupportedException(Command.UNBUNDLE);
}
}

View File

@@ -0,0 +1,67 @@
/**
* 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.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.api.UnbundleResponse;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
/**
* Service provider implementation for the unbundle command.
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
* @since 1.43
*/
public interface UnbundleCommand
{
/**
* The unbundle command can restore an empty repository from a bundle.
*
*
* @param request unbundle request
*
* @return unbundle response
*
* @throws IOException
* @throws RepositoryException
*/
public UnbundleResponse unbundle(UnbundleCommandRequest request)
throws IOException, RepositoryException;
}

View File

@@ -0,0 +1,110 @@
/**
* 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.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.common.io.ByteSource;
/**
* Request object for the unbundle command.
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
* @since 1.43
*/
public final class UnbundleCommandRequest
{
/**
* Constructs a new unbundle command request.
*
*
* @param archive byte source archive
*/
public UnbundleCommandRequest(ByteSource archive)
{
this.archive = archive;
}
//~--- methods --------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final UnbundleCommandRequest other = (UnbundleCommandRequest) obj;
return Objects.equal(archive, other.archive);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return Objects.hashCode(archive);
}
//~--- get methods ----------------------------------------------------------
/**
* Returns the archive as {@link ByteSource}.
*
*
* @return {@link ByteSource} archive
*/
ByteSource getArchive()
{
return archive;
}
//~--- fields ---------------------------------------------------------------
/** byte source archive */
private final ByteSource archive;
}

View File

@@ -94,6 +94,20 @@ public class RestUrlProvider implements UrlProvider
return HttpUtil.append(baseUrl, PART_AUTHENTICATION).concat(extension);
}
/**
* Method description
*
*
* @return
*
* @since 1.43
*/
@Override
public String getBaseUrl()
{
return baseUrl;
}
/**
* Method description
*

View File

@@ -48,6 +48,16 @@ public interface UrlProvider
*/
public String getAuthenticationUrl();
/**
* Method description
*
*
* @return
*
* @since 1.43
*/
public String getBaseUrl();
/**
* Method description
*
@@ -77,7 +87,7 @@ public interface UrlProvider
*
*
* @return
*
*
* @since 1.41
*/
public SecurityUrlProvider getSecurityUrlProvider();

View File

@@ -88,6 +88,20 @@ public class WUIUrlProvider implements UrlProvider
return baseUrl;
}
/**
* Method description
*
*
* @return
*
* @since 1.43
*/
@Override
public String getBaseUrl()
{
return baseUrl;
}
/**
* Method description
*

View File

@@ -73,6 +73,12 @@ public final class HttpUtil
/** Field description */
public static final String ENCODING = "UTF-8";
/**
* location header
* @since 1.43
*/
public static final String HEADER_LOCATION = "Location";
/**
* header for identifying the scm-manager client
* @since 1.19

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.web.filter;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Helper methods for implementations of the {@link AutoLoginModule}.
*
* @author Sebastian Sdorra <sebastian.sdorra@gmail.com>
*
* @since 1.42
*/
public final class AutoLoginModules
{
/** Field description */
private static final String FLAG_COMPLETE =
AutoLoginModules.class.getName().concat("complete");
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
private AutoLoginModules() {}
//~--- methods --------------------------------------------------------------
/**
* Mark the request as completed. No further actions will be executed.
*
* @param request http servlet request
*/
public static void markAsComplete(HttpServletRequest request)
{
request.setAttribute(FLAG_COMPLETE, Boolean.TRUE);
}
/**
* Sends a redirect to the specified url and marks the request as completed.
* This method is useful for SSO solutions which have to redirect the user
* to a central login page. This method must be used in favor of
* {@link HttpServletResponse#sendRedirect(java.lang.String)} which could
* result in an error.
*
* @param request http servlet request
* @param response http servlet response
* @param url redirect target
*
* @throws IOException if client could not be redirected
*/
public static void sendRedirect(HttpServletRequest request,
HttpServletResponse response, String url)
throws IOException
{
markAsComplete(request);
response.sendRedirect(url);
}
//~--- get methods ----------------------------------------------------------
/**
* Returns {@code true} is the request is marked as complete.
*
* @param request http servlet request
*
* @return {@code true} if request is complete
*/
public static boolean isComplete(HttpServletRequest request)
{
return request.getAttribute(FLAG_COMPLETE) != null;
}
}

View File

@@ -126,60 +126,17 @@ public class BasicAuthenticationFilter extends AutoLoginFilter
throws IOException, ServletException
{
Subject subject = SecurityUtils.getSubject();
// get authenticated user or process AutoLoginModule's
User user = getAuthenticatedUser(request, response);
// Fallback to basic authentication scheme
if (user == null)
if (AutoLoginModules.isComplete(request))
{
String authentication = request.getHeader(HEADER_AUTHORIZATION);
if (Util.startWithIgnoreCase(authentication, AUTHORIZATION_BASIC_PREFIX))
{
if (logger.isTraceEnabled())
{
logger.trace(
"found basic authorization header, start authentication");
}
user = authenticate(request, response, subject, authentication);
if (logger.isTraceEnabled())
{
if (user != null)
{
logger.trace("user {} successfully authenticated", user.getName());
}
else
{
logger.trace("authentcation failed, user object is null");
}
}
}
else if ((configuration != null)
&& configuration.isAnonymousAccessEnabled())
{
if (logger.isTraceEnabled())
{
logger.trace("anonymous access granted");
}
user = SCMContext.ANONYMOUS;
}
}
if (user == null)
{
if (logger.isTraceEnabled())
{
logger.trace("could not find user send unauthorized");
}
handleUnauthorized(request, response, chain);
logger.debug("request marked as complete from an auto login module");
}
else
{
chain.doFilter(new SecurityHttpServletRequestWrapper(request, user),
response);
// process with basic authentication
processRequest(request, response, chain, subject, user);
}
}
@@ -318,6 +275,79 @@ public class BasicAuthenticationFilter extends AutoLoginFilter
return user;
}
/**
* Method description
*
*
* @param request
* @param response
* @param chain
* @param subject
* @param user
*
* @throws IOException
* @throws ServletException
*/
private void processRequest(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Subject subject, User user)
throws IOException, ServletException
{
// Fallback to basic authentication scheme
if (user == null)
{
String authentication = request.getHeader(HEADER_AUTHORIZATION);
if (Util.startWithIgnoreCase(authentication, AUTHORIZATION_BASIC_PREFIX))
{
if (logger.isTraceEnabled())
{
logger.trace(
"found basic authorization header, start authentication");
}
user = authenticate(request, response, subject, authentication);
if (logger.isTraceEnabled())
{
if (user != null)
{
logger.trace("user {} successfully authenticated", user.getName());
}
else
{
logger.trace("authentcation failed, user object is null");
}
}
}
else if ((configuration != null)
&& configuration.isAnonymousAccessEnabled())
{
if (logger.isTraceEnabled())
{
logger.trace("anonymous access granted");
}
user = SCMContext.ANONYMOUS;
}
}
if (user == null)
{
if (logger.isTraceEnabled())
{
logger.trace("could not find user send unauthorized");
}
handleUnauthorized(request, response, chain);
}
else
{
chain.doFilter(new SecurityHttpServletRequestWrapper(request, user),
response);
}
}
//~--- fields ---------------------------------------------------------------
/** scm main configuration */

View File

@@ -39,7 +39,7 @@
<body>
<div style="text-align: justify;">
<p>
<img src="http://download.scm-manager.org/images/logo/scm-manager_logo.jpg" />
<img src="//download.scm-manager.org/images/logo/scm-manager_logo.jpg" alt="SCM-Manager" />
</p>
</div>
<p></p>
@@ -62,7 +62,7 @@
<h3>Architecture</h3>
<div style="text-align: center;">
<img src="http://download.scm-manager.org/images/architecture/architecture.001.png" alt="Architecture Overview">
<img src="//download.scm-manager.org/images/architecture/architecture.001.png" alt="Architecture Overview">
</div>
</body>
</html>

View File

@@ -33,4 +33,7 @@ http://bitbucket.org/sdorra/scm-manager
<extension-point>
<class>javax.servlet.ServletContextListener</class>
</extension-point>
<extension-point>
<class>javax.servlet.http.HttpSessionListener</class>
</extension-point>
</module>

View File

@@ -37,8 +37,8 @@ package sonia.scm.repository;
import com.google.common.collect.Lists;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -52,8 +52,9 @@ import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
*
@@ -109,76 +110,27 @@ public class GitHookChangesetCollector
for (ReceiveCommand rc : receiveCommands)
{
//J-
logger.trace("handle receive command, type={}, ref={}, result={}",
new Object[] {
rc.getType(),
rc.getRefName(),
rc.getResult()
}
);
//J+
ObjectId newId = rc.getNewId();
String branch = GitUtil.getBranch(rc.getRefName());
walk.reset();
walk.sort(RevSort.TOPO);
walk.sort(RevSort.REVERSE, true);
if (logger.isTraceEnabled())
if (rc.getType() != ReceiveCommand.Type.DELETE)
{
logger.trace("mark {} as start for rev walk", newId.getName());
}
walk.markStart(walk.parseCommit(newId));
ObjectId oldId = rc.getOldId();
if ((oldId != null) &&!oldId.equals(ObjectId.zeroId()))
{
if (logger.isTraceEnabled())
try
{
logger.trace("mark {} as uninteresting for rev walk",
oldId.getName());
collectChangesets(changesets, converter, walk, rc);
}
walk.markUninteresting(walk.parseCommit(oldId));
}
for (ObjectId id : getExistingObjects(rpack))
{
if (logger.isTraceEnabled())
catch (IOException ex)
{
logger.trace("mark {} as uninteresting for rev walk", id.getName());
StringBuilder builder = new StringBuilder();
builder.append("could not handle receive command, type=");
builder.append(rc.getType()).append(", ref=");
builder.append(rc.getRefName()).append(", result=");
builder.append(rc.getResult());
logger.error(builder.toString(), ex);
}
walk.markUninteresting(walk.parseCommit(id));
}
RevCommit commit = walk.next();
List<String> branches = Lists.newArrayList(branch);
while (commit != null)
else
{
// parse commit body to avoid npe
walk.parseBody(commit);
Changeset changeset = converter.createChangeset(commit, branches);
if (logger.isTraceEnabled())
{
logger.trace("retrive commit {} for hook", changeset.getId());
}
changesets.add(changeset);
commit = walk.next();
logger.debug("skip delete of branch {}", rc.getRefName());
}
}
}
@@ -195,38 +147,78 @@ public class GitHookChangesetCollector
return changesets;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param rpack
* @param changesets
* @param converter
* @param walk
* @param rc
*
* @return
* @throws IOException
* @throws IncorrectObjectTypeException
*/
private List<ObjectId> getExistingObjects(ReceivePack rpack)
private void collectChangesets(List<Changeset> changesets,
GitChangesetConverter converter, RevWalk walk, ReceiveCommand rc)
throws IncorrectObjectTypeException, IOException
{
List<ObjectId> existingObjects = Lists.newArrayList();
if (existingObjects == null)
{
Map<String, Ref> refs = rpack.getRepository().getAllRefs();
for (Ref r : refs.values())
{
existingObjects.add(r.getObjectId());
//J-
logger.trace("handle receive command, type={}, ref={}, result={}",
new Object[] {
rc.getType(),
rc.getRefName(),
rc.getResult()
}
);
//J+
ObjectId newId = rc.getNewId();
String branch = GitUtil.getBranch(rc.getRefName());
walk.reset();
walk.sort(RevSort.TOPO);
walk.sort(RevSort.REVERSE, true);
logger.trace("mark {} as start for rev walk", newId.getName());
walk.markStart(walk.parseCommit(newId));
ObjectId oldId = rc.getOldId();
if ((oldId != null) &&!oldId.equals(ObjectId.zeroId()))
{
logger.trace("mark {} as uninteresting for rev walk", oldId.getName());
walk.markUninteresting(walk.parseCommit(oldId));
}
return existingObjects;
RevCommit commit = walk.next();
List<String> branches = Lists.newArrayList(branch);
while (commit != null)
{
// parse commit body to avoid npe
walk.parseBody(commit);
Changeset changeset = converter.createChangeset(commit, branches);
logger.trace("retrive commit {} for hook", changeset.getId());
changesets.add(changeset);
commit = walk.next();
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private List<ReceiveCommand> receiveCommands;
private final List<ReceiveCommand> receiveCommands;
/** Field description */
private ReceivePack rpack;
private final ReceivePack rpack;
}

View File

@@ -695,6 +695,32 @@ public final class GitUtil
//J+
}
/**
* Method description
*
*
* @param ref
*
* @return
*/
public static boolean isHead(String ref)
{
return ref.startsWith(REF_HEAD_PREFIX);
}
/**
* Method description
*
*
* @param id
*
* @return
*/
public static boolean isValidObjectId(ObjectId id)
{
return (id != null) &&!id.equals(ObjectId.zeroId());
}
//~--- methods --------------------------------------------------------------
/**

View File

@@ -48,6 +48,8 @@ import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.RepositoryException;
//~--- JDK imports ------------------------------------------------------------
@@ -61,17 +63,17 @@ import java.util.Collection;
*
* @author Sebastian Sdorra
*/
public abstract class AbstractPushOrPullCommand extends AbstractGitCommand
public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand
{
/** Field description */
private static final String SCHEME = "scm://";
/**
* the logger for AbstractPushOrPullCommand
* the logger for AbstractGitPushOrPullCommand
*/
private static final Logger logger =
LoggerFactory.getLogger(AbstractPushOrPullCommand.class);
LoggerFactory.getLogger(AbstractGitPushOrPullCommand.class);
//~--- constructors ---------------------------------------------------------
@@ -79,36 +81,38 @@ public abstract class AbstractPushOrPullCommand extends AbstractGitCommand
* Constructs ...
*
*
* @param handler
* @param context
* @param repository
*/
public AbstractPushOrPullCommand(GitContext context,
sonia.scm.repository.Repository repository)
protected AbstractGitPushOrPullCommand(GitRepositoryHandler handler,
GitContext context, sonia.scm.repository.Repository repository)
{
super(context, repository);
this.handler = handler;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param source
* @param target
* @param remoteUrl
*
* @return
*
* @throws IOException
* @throws RepositoryException
*/
protected long push(Repository source, File target)
protected long push(Repository source, String remoteUrl)
throws IOException, RepositoryException
{
Git git = Git.wrap(source);
org.eclipse.jgit.api.PushCommand push = git.push();
push.setPushAll().setPushTags();
push.setRemote(SCHEME.concat(target.getAbsolutePath()));
push.setRemote(remoteUrl);
long counter = -1;
@@ -158,6 +162,61 @@ public abstract class AbstractPushOrPullCommand extends AbstractGitCommand
return remoteRepository;
}
/**
* Method description
*
*
* @param request
*
* @return
*/
protected String getRemoteUrl(RemoteCommandRequest request)
{
String url;
sonia.scm.repository.Repository remRepo = request.getRemoteRepository();
if (remRepo != null)
{
url = getRemoteUrl(remRepo);
}
else if (request.getRemoteUrl() != null)
{
url = request.getRemoteUrl().toExternalForm();
}
else
{
throw new IllegalArgumentException("repository or url is requiered");
}
return url;
}
/**
* Method description
*
*
* @param directory
*
* @return
*/
protected String getRemoteUrl(File directory)
{
return SCHEME.concat(directory.getAbsolutePath());
}
/**
* Method description
*
*
* @param repository
*
* @return
*/
protected String getRemoteUrl(sonia.scm.repository.Repository repository)
{
return getRemoteUrl(handler.getDirectory(repository));
}
//~--- methods --------------------------------------------------------------
/**
@@ -195,21 +254,24 @@ public abstract class AbstractPushOrPullCommand extends AbstractGitCommand
{
long counter = 0;
try
if (GitUtil.isHead(update.getRemoteName()))
{
org.eclipse.jgit.api.LogCommand log = git.log();
ObjectId oldId = update.getExpectedOldObjectId();
if (oldId != null)
try
{
log.not(oldId);
}
org.eclipse.jgit.api.LogCommand log = git.log();
ObjectId oldId = update.getExpectedOldObjectId();
ObjectId newId = update.getNewObjectId();
if (GitUtil.isValidObjectId(oldId))
{
log.not(oldId);
}
if (newId != null)
{
log.add(newId);
ObjectId newId = update.getNewObjectId();
if (GitUtil.isValidObjectId(newId))
{
log.add(newId);
}
Iterable<RevCommit> commits = log.call();
@@ -217,18 +279,24 @@ public abstract class AbstractPushOrPullCommand extends AbstractGitCommand
{
counter += Iterables.size(commits);
}
}
else
{
logger.warn("update without new object id");
}
logger.trace("counting {} commits for ref update {}", counter, update);
}
catch (Exception ex)
{
logger.error("could not count pushed/pulled changesets", ex);
}
}
catch (Exception ex)
else
{
logger.error("could not count pushed/pulled changesets", ex);
logger.debug("do not count non branch ref update {}", update);
}
return counter;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
protected GitRepositoryHandler handler;
}

View File

@@ -36,8 +36,19 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.TagOpt;
import org.eclipse.jgit.transport.TrackingRefUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.GitUtil;
@@ -50,27 +61,37 @@ import sonia.scm.repository.api.PullResponse;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
*
* @author Sebastian Sdorra
*/
public class GitPullCommand extends AbstractPushOrPullCommand
public class GitPullCommand extends AbstractGitPushOrPullCommand
implements PullCommand
{
/** Field description */
private static final String REF_SPEC = "refs/heads/*:refs/heads/*";
/** Field description */
private static final Logger logger =
LoggerFactory.getLogger(GitPullCommand.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param repositoryHandler
* @param handler
* @param context
* @param repository
*/
public GitPullCommand(GitRepositoryHandler repositoryHandler,
GitContext context, Repository repository)
public GitPullCommand(GitRepositoryHandler handler, GitContext context,
Repository repository)
{
super(context, repository);
this.repositoryHandler = repositoryHandler;
super(handler, context, repository);
}
//~--- methods --------------------------------------------------------------
@@ -90,18 +111,134 @@ public class GitPullCommand extends AbstractPushOrPullCommand
public PullResponse pull(PullCommandRequest request)
throws IOException, RepositoryException
{
Repository sourceRepository = getRemoteRepository(request);
PullResponse response;
Repository sourceRepository = request.getRemoteRepository();
File sourceDirectory = repositoryHandler.getDirectory(sourceRepository);
if (sourceRepository != null)
{
response = pullFromScmRepository(sourceRepository);
}
else if (request.getRemoteUrl() != null)
{
response = pullFromUrl(request.getRemoteUrl());
}
else
{
throw new IllegalArgumentException("repository or url is required");
}
return response;
}
/**
* Method description
*
*
* @param git
* @param result
* @param fetch
*
* @return
*
* @throws RepositoryException
*/
private PullResponse convert(Git git, FetchResult fetch)
throws RepositoryException
{
long counter = 0l;
for (TrackingRefUpdate tru : fetch.getTrackingRefUpdates())
{
counter += count(git, tru);
}
logger.debug("received {} changesets by pull", counter);
return new PullResponse(counter);
}
/**
* Method description
*
*
* @param git
* @param tru
*
* @return
*/
private long count(Git git, TrackingRefUpdate tru)
{
long counter = 0;
if (GitUtil.isHead(tru.getLocalName()))
{
try
{
org.eclipse.jgit.api.LogCommand log = git.log();
ObjectId oldId = tru.getOldObjectId();
if (GitUtil.isValidObjectId(oldId))
{
log.not(oldId);
}
ObjectId newId = tru.getNewObjectId();
if (GitUtil.isValidObjectId(newId))
{
log.add(newId);
}
Iterable<RevCommit> commits = log.call();
if (commits != null)
{
counter += Iterables.size(commits);
}
logger.trace("counting {} commits for ref update {}", counter, tru);
}
catch (Exception ex)
{
logger.error("could not count pushed/pulled changesets", ex);
}
}
else
{
logger.debug("do not count non branch ref update {}", tru);
}
return counter;
}
/**
* Method description
*
*
* @param sourceRepository
*
* @return
*
* @throws IOException
* @throws RepositoryException
*/
private PullResponse pullFromScmRepository(Repository sourceRepository)
throws IOException, RepositoryException
{
File sourceDirectory = handler.getDirectory(sourceRepository);
Preconditions.checkArgument(sourceDirectory.exists(),
"source repository directory does not exists");
File targetDirectory = repositoryHandler.getDirectory(repository);
File targetDirectory = handler.getDirectory(repository);
Preconditions.checkArgument(sourceDirectory.exists(),
"target repository directory does not exists");
logger.debug("pull changes from {} to {}",
sourceDirectory.getAbsolutePath(), repository.getId());
PullResponse response = null;
org.eclipse.jgit.lib.Repository source = null;
@@ -109,7 +246,7 @@ public class GitPullCommand extends AbstractPushOrPullCommand
try
{
source = Git.open(sourceDirectory).getRepository();
response = new PullResponse(push(source, targetDirectory));
response = new PullResponse(push(source, getRemoteUrl(targetDirectory)));
}
finally
{
@@ -119,8 +256,42 @@ public class GitPullCommand extends AbstractPushOrPullCommand
return response;
}
//~--- fields ---------------------------------------------------------------
/**
* Method description
*
*
* @param url
*
* @return
*
* @throws IOException
* @throws RepositoryException
*/
private PullResponse pullFromUrl(URL url)
throws IOException, RepositoryException
{
logger.debug("pull changes from {} to {}", url, repository.getId());
/** Field description */
private GitRepositoryHandler repositoryHandler;
PullResponse response;
Git git = Git.wrap(open());
try
{
//J-
FetchResult result = git.fetch()
.setRefSpecs(new RefSpec(REF_SPEC))
.setRemote(url.toExternalForm())
.setTagOpt(TagOpt.FETCH_TAGS)
.call();
//J+
response = convert(git, result);
}
catch (GitAPIException ex)
{
throw new RepositoryException("error durring pull", ex);
}
return response;
}
}

View File

@@ -30,11 +30,13 @@
*/
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -43,17 +45,22 @@ import sonia.scm.repository.api.PushResponse;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
/**
*
* @author Sebastian Sdorra
*/
public class GitPushCommand extends AbstractPushOrPullCommand
public class GitPushCommand extends AbstractGitPushOrPullCommand
implements PushCommand
{
/** Field description */
private static final Logger logger =
LoggerFactory.getLogger(GitPushCommand.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
@@ -65,7 +72,7 @@ public class GitPushCommand extends AbstractPushOrPullCommand
public GitPushCommand(GitRepositoryHandler handler, GitContext context,
Repository repository)
{
super(context, repository);
super(handler, context, repository);
this.handler = handler;
}
@@ -86,17 +93,10 @@ public class GitPushCommand extends AbstractPushOrPullCommand
public PushResponse push(PushCommandRequest request)
throws IOException, RepositoryException
{
Repository target = getRemoteRepository(request);
File targetDirectory = handler.getDirectory(target);
String remoteUrl = getRemoteUrl(request);
Preconditions.checkArgument(targetDirectory.exists(),
"target repository directory does not exists");
logger.debug("push changes from {} to {}", repository.getId(), remoteUrl);
return new PushResponse(push(open(), targetDirectory));
return new PushResponse(push(open(), remoteUrl));
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private GitRepositoryHandler handler;
}

View File

@@ -44,7 +44,6 @@ import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.Branch;
import sonia.scm.repository.Branches;
import sonia.scm.repository.Changeset;
@@ -60,6 +59,7 @@ import sonia.scm.template.TemplateEngineFactory;
import sonia.scm.url.RepositoryUrlProvider;
import sonia.scm.url.UrlProvider;
import sonia.scm.url.UrlProviderFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
@@ -85,7 +85,8 @@ public class GitRepositoryViewer
public static final String MIMETYPE_HTML = "text/html";
/** Field description */
public static final String RESOURCE_GITINDEX = "/sonia/scm/git.index.mustache";
public static final String RESOURCE_GITINDEX =
"/sonia/scm/git.index.mustache";
/** Field description */
private static final int CHANGESET_PER_BRANCH = 10;
@@ -104,16 +105,13 @@ public class GitRepositoryViewer
*
* @param templateEngineFactory
* @param repositoryServiceFactory
* @param configuration
*/
@Inject
public GitRepositoryViewer(TemplateEngineFactory templateEngineFactory,
RepositoryServiceFactory repositoryServiceFactory,
ScmConfiguration configuration)
RepositoryServiceFactory repositoryServiceFactory)
{
this.templateEngineFactory = templateEngineFactory;
this.repositoryServiceFactory = repositoryServiceFactory;
this.configuration = configuration;
}
//~--- methods --------------------------------------------------------------
@@ -135,7 +133,9 @@ public class GitRepositoryViewer
throws RepositoryException, IOException
{
String baseUrl = configuration.getBaseUrl();
String baseUrl = HttpUtil.getCompleteUrl(request);
logger.trace("render git repository quick view with base url {}", baseUrl);
UrlProvider urlProvider = UrlProviderFactory.createUrlProvider(baseUrl,
UrlProviderFactory.TYPE_WUI);
@@ -159,6 +159,7 @@ public class GitRepositoryViewer
try
{
response.setCharacterEncoding("UTF-8");
writer = response.getWriter();
template.execute(writer, env);
}
@@ -453,9 +454,6 @@ public class GitRepositoryViewer
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ScmConfiguration configuration;
/** Field description */
private final RepositoryServiceFactory repositoryServiceFactory;

View File

@@ -37,7 +37,6 @@ package sonia.scm.repository.spi;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.ScmTransportProtocol;
import org.junit.Test;
@@ -106,9 +105,4 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase
return new GitPushCommand(handler, new GitContext(outgoingDirectory),
outgoingRepository);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ScmTransportProtocol proto;
}

View File

@@ -23,6 +23,12 @@
<groupId>com.aragost.javahg</groupId>
<artifactId>javahg</artifactId>
<version>0.8-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- test scope -->

View File

@@ -0,0 +1,99 @@
/**
* 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.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
/**
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
*/
public class AbstractHgPushOrPullCommand extends AbstractCommand
{
/**
* Constructs ...
*
*
* @param handler
* @param context
* @param repository
*/
protected AbstractHgPushOrPullCommand(HgRepositoryHandler handler,
HgCommandContext context, Repository repository)
{
super(context, repository);
this.handler = handler;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param request
*
* @return
*/
protected String getRemoteUrl(RemoteCommandRequest request)
{
String url;
Repository repo = request.getRemoteRepository();
if (repo != null)
{
url =
handler.getDirectory(request.getRemoteRepository()).getAbsolutePath();
}
else if (request.getRemoteUrl() != null)
{
url = request.getRemoteUrl().toExternalForm();
}
else
{
throw new IllegalArgumentException("url or repository is required");
}
return url;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
protected final HgRepositoryHandler handler;
}

View File

@@ -30,6 +30,7 @@
*/
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
@@ -37,6 +38,9 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Changeset;
import com.aragost.javahg.commands.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
@@ -44,7 +48,6 @@ import sonia.scm.repository.api.PullResponse;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.util.Collections;
@@ -54,9 +57,16 @@ import java.util.List;
*
* @author Sebastian Sdorra
*/
public class HgPullCommand extends AbstractCommand implements PullCommand
public class HgPullCommand extends AbstractHgPushOrPullCommand
implements PullCommand
{
/** Field description */
private static final Logger logger =
LoggerFactory.getLogger(HgPullCommand.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
@@ -68,8 +78,7 @@ public class HgPullCommand extends AbstractCommand implements PullCommand
public HgPullCommand(HgRepositoryHandler handler, HgCommandContext context,
Repository repository)
{
super(context, repository);
this.handler = handler;
super(handler, context, repository);
}
//~--- methods --------------------------------------------------------------
@@ -90,14 +99,15 @@ public class HgPullCommand extends AbstractCommand implements PullCommand
public PullResponse pull(PullCommandRequest request)
throws RepositoryException, IOException
{
File remoteRepository = handler.getDirectory(request.getRemoteRepository());
String url = getRemoteUrl(request);
logger.debug("pull changes from {} to {}", url, getRepository().getId());
List<Changeset> result = Collections.EMPTY_LIST;
try
{
result = com.aragost.javahg.commands.PullCommand.on(open()).execute(
remoteRepository.getAbsolutePath());
result = com.aragost.javahg.commands.PullCommand.on(open()).execute(url);
}
catch (ExecutionException ex)
{
@@ -106,9 +116,4 @@ public class HgPullCommand extends AbstractCommand implements PullCommand
return new PullResponse(result.size());
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final HgRepositoryHandler handler;
}

View File

@@ -30,6 +30,7 @@
*/
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
@@ -37,6 +38,9 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Changeset;
import com.aragost.javahg.commands.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
@@ -44,7 +48,6 @@ import sonia.scm.repository.api.PushResponse;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.util.Collections;
@@ -54,9 +57,16 @@ import java.util.List;
*
* @author Sebastian Sdorra
*/
public class HgPushCommand extends AbstractCommand implements PushCommand
public class HgPushCommand extends AbstractHgPushOrPullCommand
implements PushCommand
{
/** Field description */
private static final Logger logger =
LoggerFactory.getLogger(HgPushCommand.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
@@ -68,8 +78,7 @@ public class HgPushCommand extends AbstractCommand implements PushCommand
public HgPushCommand(HgRepositoryHandler handler, HgCommandContext context,
Repository repository)
{
super(context, repository);
this.handler = handler;
super(handler, context, repository);
}
//~--- methods --------------------------------------------------------------
@@ -90,14 +99,15 @@ public class HgPushCommand extends AbstractCommand implements PushCommand
public PushResponse push(PushCommandRequest request)
throws RepositoryException, IOException
{
File remoteRepository = handler.getDirectory(request.getRemoteRepository());
String url = getRemoteUrl(request);
logger.debug("push changes from {} to {}", getRepository().getId(), url);
List<Changeset> result = Collections.EMPTY_LIST;
try
{
result = com.aragost.javahg.commands.PushCommand.on(open()).execute(
remoteRepository.getAbsolutePath());
result = com.aragost.javahg.commands.PushCommand.on(open()).execute(url);
}
catch (ExecutionException ex)
{
@@ -106,9 +116,4 @@ public class HgPushCommand extends AbstractCommand implements PushCommand
return new PushResponse(result.size());
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final HgRepositoryHandler handler;
}

View File

@@ -0,0 +1,152 @@
/**
* 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.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.ByteSink;
import com.google.common.io.Closeables;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.admin.SVNAdminClient;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.SvnUtil;
import sonia.scm.repository.api.BundleResponse;
import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
/**
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
*/
public class SvnBundleCommand extends AbstractSvnCommand
implements BundleCommand
{
/**
* Constructs ...
*
*
* @param context
* @param repository
*/
public SvnBundleCommand(SvnContext context, Repository repository)
{
super(context, repository);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param adminClient
* @param repository
* @param target
*
* @throws IOException
* @throws SVNException
*/
private static void dump(SVNAdminClient adminClient, File repository,
ByteSink target)
throws SVNException, IOException
{
OutputStream outputStream = null;
try
{
outputStream = target.openBufferedStream();
adminClient.doDump(repository, outputStream, SVNRevision.create(-1l),
SVNRevision.HEAD, false, false);
}
finally
{
Closeables.close(outputStream, true);
}
}
/**
* Method description
*
*
* @param request
*
* @return
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public BundleResponse bundle(BundleCommandRequest request)
throws IOException, RepositoryException
{
ByteSink archive = checkNotNull(request.getArchive(),
"archive is required");
BundleResponse response;
SVNClientManager clientManager = null;
try
{
clientManager = SVNClientManager.newInstance();
SVNAdminClient adminClient = clientManager.getAdminClient();
dump(adminClient, context.getDirectory(), archive);
response = new BundleResponse(context.open().getLatestRevision());
}
catch (SVNException ex)
{
throw new IOException("could not create dump", ex);
}
finally
{
SvnUtil.dispose(clientManager);
}
return response;
}
}

View File

@@ -56,9 +56,12 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
{
/** Field description */
public static final Set<Command> COMMANDS = ImmutableSet.of(Command.BLAME,
Command.BROWSE, Command.CAT,
Command.DIFF, Command.LOG);
//J-
public static final Set<Command> COMMANDS = ImmutableSet.of(
Command.BLAME, Command.BROWSE, Command.CAT, Command.DIFF,
Command.LOG, Command.BUNDLE, Command.UNBUNDLE
);
//J+
//~--- constructors ---------------------------------------------------------
@@ -116,6 +119,18 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
return new SvnBrowseCommand(context, repository);
}
/**
* Method description
*
*
* @return
*/
@Override
public BundleCommand getBundleCommand()
{
return new SvnBundleCommand(context, repository);
}
/**
* Method description
*
@@ -164,6 +179,18 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
return COMMANDS;
}
/**
* Method description
*
*
* @return
*/
@Override
public UnbundleCommand getUnbundleCommand()
{
return new SvnUnbundleCommand(context, repository);
}
//~--- fields ---------------------------------------------------------------
/** Field description */

View File

@@ -0,0 +1,161 @@
/**
* 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.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.ByteSource;
import com.google.common.io.Closeables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.admin.SVNAdminClient;
import sonia.scm.repository.Repository;
import sonia.scm.repository.SvnUtil;
import sonia.scm.repository.api.UnbundleResponse;
import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
*/
public class SvnUnbundleCommand extends AbstractSvnCommand
implements UnbundleCommand
{
/** Field description */
private static final Logger logger =
LoggerFactory.getLogger(SvnUnbundleCommand.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param context
* @param repository
*/
public SvnUnbundleCommand(SvnContext context, Repository repository)
{
super(context, repository);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
*
* @return
*
* @throws IOException
*/
@Override
public UnbundleResponse unbundle(UnbundleCommandRequest request)
throws IOException
{
ByteSource archive = checkNotNull(request.getArchive(),
"archive is required");
logger.debug("archive repository {} to {}", context.getDirectory(),
archive);
UnbundleResponse response;
SVNClientManager clientManager = null;
try
{
clientManager = SVNClientManager.newInstance();
SVNAdminClient adminClient = clientManager.getAdminClient();
restore(adminClient, archive, context.getDirectory());
response = new UnbundleResponse(context.open().getLatestRevision());
}
catch (SVNException ex)
{
throw new IOException("could not restore dump", ex);
}
finally
{
SvnUtil.dispose(clientManager);
}
return response;
}
/**
* Method description
*
*
* @param adminClient
* @param dump
* @param repository
*
* @throws IOException
* @throws SVNException
*/
private void restore(SVNAdminClient adminClient, ByteSource dump,
File repository)
throws SVNException, IOException
{
InputStream inputStream = null;
try
{
inputStream = dump.openBufferedStream();
adminClient.doLoad(repository, inputStream);
}
finally
{
Closeables.close(inputStream, true);
}
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.ByteSink;
import com.google.common.io.Files;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.api.BundleResponse;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
/**
*
* @author Sebastian Sdorra
*/
public class SvnBundleCommandTest extends AbstractSvnCommandTestBase
{
/**
* Method description
*
*
* @throws IOException
* @throws RepositoryException
*/
@Test
public void testBundle() throws IOException, RepositoryException
{
File file = temp.newFile();
ByteSink sink = Files.asByteSink(file);
BundleCommandRequest req = new BundleCommandRequest(sink);
BundleResponse res = new SvnBundleCommand(createContext(),
repository).bundle(req);
assertThat(res, notNullValue());
assertThat(res.getChangesetCount(), is(5l));
assertTrue("file does not exists", file.exists());
assertThat(file.length(), greaterThan(0l));
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@Rule
public TemporaryFolder temp = new TemporaryFolder();
}

View File

@@ -0,0 +1,142 @@
/**
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.Files;
import org.junit.Test;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.SvnUtil;
import sonia.scm.repository.api.UnbundleResponse;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
/**
*
* @author Sebastian Sdorra
*/
public class SvnUnbundleCommandTest extends AbstractSvnCommandTestBase
{
/**
* Method description
*
*
* @throws IOException
* @throws RepositoryException
* @throws SVNException
*/
@Test
public void testUnbundle()
throws IOException, RepositoryException, SVNException
{
File bundle = bundle();
SvnContext ctx = createEmptyContext();
//J-
UnbundleResponse res = new SvnUnbundleCommand(
ctx,
repository
).unbundle(
new UnbundleCommandRequest(
Files.asByteSource(bundle)
)
);
//J+
assertThat(res, notNullValue());
assertThat(res.getChangesetCount(), is(5l));
SVNRepository repo = ctx.open();
assertThat(repo.getLatestRevision(), is(5l));
SvnUtil.closeSession(repo);
}
/**
* Method description
*
*
* @return
*
* @throws IOException
* @throws RepositoryException
*/
private File bundle() throws IOException, RepositoryException
{
File file = tempFolder.newFile();
//J-
new SvnBundleCommand(
createContext(),
repository
).bundle(
new BundleCommandRequest(
Files.asByteSink(file)
)
);
//J+
return file;
}
/**
* Method description
*
*
* @return
*
* @throws IOException
* @throws SVNException
*/
private SvnContext createEmptyContext() throws IOException, SVNException
{
File folder = tempFolder.newFolder();
SVNRepositoryFactory.createLocalRepository(folder, true, true);
return new SvnContext(folder);
}
}

View File

@@ -127,5 +127,90 @@
</Set>
</New>
</Set>
<<<<<<< mine
=======
<!-- request logging -->
<!--
<Ref id="RequestLog">
<Set name="requestLog">
<New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
<Arg><SystemProperty name="basedir" default="."/>/var/log/yyyy_mm_dd.request.log</Arg>
<Set name="retainDays">90</Set>
<Set name="append">true</Set>
<Set name="extended">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>
</Set>
</Ref>
-->
<!-- mod_proxy_ajp or mod_jk -->
<!--
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.ajp.Ajp13SocketConnector">
<Set name="port">8009</Set>
</New>
</Arg>
</Call>
-->
<!-- SSL-Connector -->
<!--
Documentation for the SSL-Connector:
http://wiki.eclipse.org/Jetty/Reference/SSL_Connectors
-->
<!--
Besure SSLv3 protocol is excluded to avoid POODLE vulnerability.
See https://groups.google.com/d/msg/scmmanager/sX_Ydy-wAPA/-Dvs5i7RHtQJ
-->
<!--
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ssl.SslSelectChannelConnector">
<Arg>
<New class="org.eclipse.jetty.http.ssl.SslContextFactory">
<Set name="excludeProtocols">
<Array type="java.lang.String">
<Item>SSLv2Hello</Item>
<Item>SSLv3</Item>
</Array>
</Set>
</New>
</Arg>
<Set name="Port">8181</Set>
<Set name="maxIdleTime">30000</Set>
<Set name="requestHeaderSize">16384</Set>
<Set name="keystore"><SystemProperty name="basedir" default="." />/conf/keystore.jks</Set>
<Set name="password">OBF:xxx</Set>
<Set name="keyPassword">OBF:xxx</Set>
<Set name="truststore"><SystemProperty name="basedir" default="." />/conf/keystore.jks</Set>
<Set name="trustPassword">OBF:xxx</Set>
</New>
</Arg>
</Call>
-->
<!-- JMX support -->
<!--
<Call id="MBeanServer" class="java.lang.management.ManagementFactory"
name="getPlatformMBeanServer" />
<New id="MBeanContainer" class="org.eclipse.jetty.jmx.MBeanContainer">
<Arg>
<Ref id="MBeanServer" />
</Arg>
</New>
<Get id="Container" name="container">
<Call name="addEventListener">
<Arg>
<Ref id="MBeanContainer" />
</Arg>
</Call>
</Get>
-->
>>>>>>> theirs
</Configure>

View File

@@ -52,6 +52,9 @@ public class ScmServer extends Thread
/** Field description */
public static final String CONFIGURATION = "/server-config.xml";
/** Field description */
static final int GRACEFUL_TIMEOUT = 2000;
//~--- constructors ---------------------------------------------------------
/**
@@ -106,14 +109,17 @@ public class ScmServer extends Thread
}
/**
* Method description
* Stop embedded webserver. Use {@link Server#stop()} to fix windows service.
*
* @see <a href="http://goo.gl/Zfy0Ev">http://goo.gl/Zfy0Ev</a>
*/
public void stopServer()
{
try
{
server.setStopTimeout(GRACEFUL_TIMEOUT);
server.setStopAtShutdown(true);
server.stop();
initialized = false;
}
catch (Exception ex)

View File

@@ -86,7 +86,7 @@ public class ScmServerDaemon implements Daemon
public static void stop(String[] args) throws Exception
{
webserver.stopServer();
webserver.join(2000l);
webserver.join((long) ScmServer.GRACEFUL_TIMEOUT);
}
/**
@@ -113,7 +113,8 @@ public class ScmServerDaemon implements Daemon
public void init(DaemonContext context) throws DaemonInitException, Exception
{
daemonArgs = context.getArguments();
// initialize web server and open port. We have to do this in the init
// initialize web server and open port. We have to do this in the init
// method, because this method is started by jsvc with super user privileges.
webserver.init();
}

View File

@@ -490,10 +490,8 @@
<environment.profile>default</environment.profile>
<selenium.version>2.28.0</selenium.version>
<enunciate.version>1.28</enunciate.version>
<aether.version>1.13.1</aether.version>
<wagon.version>1.0</wagon.version>
<maven.version>3.0.5</maven.version>
<mustache.version>0.8.15</mustache.version>
<mustache.version>0.8.17</mustache.version>
<netbeans.hint.deploy.server>Tomcat</netbeans.hint.deploy.server>
</properties>

View File

@@ -0,0 +1,171 @@
package sonia.scm;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Sets;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.Extension;
//~--- JDK imports ------------------------------------------------------------
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* Dispatcher for {@link HttpSessionEvent}. The {@link HttpSessionListenerHolder}
* loads all registered {@link HttpSessionListener}s from the {@link Injector}
* and delegates the events to the them. {@link HttpSessionListener} can be
* registered with the {@link Extension} annotation.
*
* @author Sebastian Sdorra <sebastian.sdorra@gmail.com>
* @since 1.42
*/
public class HttpSessionListenerHolder implements HttpSessionListener
{
/** key type of the session listeners */
private static final Key<Set<HttpSessionListener>> KEY =
Key.get(new TypeLiteral<Set<HttpSessionListener>>() {}
);
/** logger for HttpSessionListenerHolder */
private static final Logger logger =
LoggerFactory.getLogger(HttpSessionListenerHolder.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs a new HttpSessionListenerHolder.
*
*/
public HttpSessionListenerHolder()
{
if (logger.isDebugEnabled())
{
logger.debug("create instance of {}",
HttpSessionListenerHolder.class.getName());
}
}
//~--- methods --------------------------------------------------------------
/**
* Delegates the create session event to all registered
* {@ĺink HttpSessionListener}s.
*
*
* @param event session event
*/
@Override
public void sessionCreated(HttpSessionEvent event)
{
if (listenerSet == null)
{
listenerSet = loadListeners(event);
}
dispatch(event, true);
}
/**
* Delegates the destroy session event to all registered
* {@ĺink HttpSessionListener}s.
*
*
* @param event session event
*/
@Override
public void sessionDestroyed(HttpSessionEvent event)
{
dispatch(event, false);
}
/**
* Dispatch session events.
*
*
* @param event session event
* @param create {@code true} if the event is a create event
*/
private void dispatch(HttpSessionEvent event, boolean create)
{
if (listenerSet != null)
{
for (HttpSessionListener listener : listenerSet)
{
if (create)
{
listener.sessionCreated(event);
}
else
{
listener.sessionDestroyed(event);
}
}
}
else
{
logger.warn(
"could not dispatch session event, because holder is not initialized");
}
}
/**
* Load listeners from {@link Injector} which is stored in the
* {@link ServletContext}.
*
*
* @param event session event
*
* @return set of session listeners
*/
private synchronized Set<HttpSessionListener> loadListeners(
HttpSessionEvent event)
{
Set<HttpSessionListener> listeners = null;
HttpSession session = event.getSession();
if (session != null)
{
Injector injector = (Injector) session.getServletContext().getAttribute(
Injector.class.getName());
if (injector != null)
{
logger.debug("load HttpSessionListeners from injector");
listeners = injector.getInstance(KEY);
}
else
{
logger.error("could not find injector in servletContext");
}
if (listeners == null)
{
listeners = Sets.newHashSet();
}
}
else
{
logger.warn("received session event without session");
}
return listeners;
}
//~--- fields ---------------------------------------------------------------
/** listener set */
private Set<HttpSessionListener> listenerSet;
}

View File

@@ -37,6 +37,8 @@ import com.google.inject.Inject;
import org.apache.shiro.SecurityUtils;
import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle;
import sonia.scm.security.KeyGenerator;
import sonia.scm.security.Role;
@@ -54,6 +56,7 @@ import javax.ws.rs.core.MediaType;
* @since 1.41
*/
@Path("security/key")
@ExternallyManagedLifecycle
public class KeyResource
{
@@ -72,7 +75,7 @@ public class KeyResource
//~--- methods --------------------------------------------------------------
/**
* Generates a unique key. This method can only executed with administration
* Generates a unique key. This method can only executed with administration
* privileges.<br />
* <br />
* <ul>
@@ -93,6 +96,6 @@ public class KeyResource
//~--- fields ---------------------------------------------------------------
/** Field description */
/** key generator */
private final KeyGenerator keyGenerator;
}

View File

@@ -35,8 +35,13 @@ package sonia.scm.api.rest.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.shiro.SecurityUtils;
import org.codehaus.enunciate.jaxrs.TypeHint;
import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle;
@@ -46,31 +51,65 @@ import org.slf4j.LoggerFactory;
import sonia.scm.NotSupportedFeatuerException;
import sonia.scm.Type;
import sonia.scm.api.rest.RestActionUploadResult;
import sonia.scm.repository.AdvancedImportHandler;
import sonia.scm.repository.ImportHandler;
import sonia.scm.repository.ImportResult;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryAllreadyExistExeption;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryHandler;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.util.SecurityUtil;
import sonia.scm.repository.RepositoryType;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.UnbundleCommandBuilder;
import sonia.scm.security.Role;
import sonia.scm.util.IOUtil;
import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.multipart.FormDataParam;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
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.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Rest resource for importing repositories.
*
* @author Sebastian Sdorra
*/
@Singleton
@Path("import/repositories")
@ExternallyManagedLifecycle
public class RepositoryImportResource
@@ -85,94 +124,525 @@ public class RepositoryImportResource
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
* Constructs a new repository import resource.
*
*
* @param manager
* @param securityContextProvider
* @param manager repository manager
* @param serviceFactory
*/
@Inject
public RepositoryImportResource(RepositoryManager manager)
public RepositoryImportResource(RepositoryManager manager,
RepositoryServiceFactory serviceFactory)
{
this.manager = manager;
this.serviceFactory = serviceFactory;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
* Imports a repository type specific bundle. The bundle file is uploaded to
* the server which is running scm-manager. After the upload has finished, the
* bundle file is passed to the {@link UnbundleCommandBuilder}. This method
* requires admin privileges.<br />
*
* Status codes:
* <ul>
* <li>201 created</li>
* <li>400 bad request, the import bundle feature is not supported by this
* type of repositories or the parameters are not valid.</li>
* <li>500 internal server error</li>
* <li>409 conflict, a repository with the name already exists.</li>
* </ul>
*
* @param uriInfo uri info
* @param type repository type
* @param name name of the repository
* @param inputStream input bundle
* @param compressed true if the bundle is gzip compressed
*
* @return empty response with location header which points to the imported
* repository
* @since 1.43
*/
@POST
@Path("{type}/bundle")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response importFromBundle(@Context UriInfo uriInfo,
@PathParam("type") String type, @FormDataParam("name") String name,
@FormDataParam("bundle") InputStream inputStream, @QueryParam("compressed")
@DefaultValue("false") boolean compressed)
{
Repository repository = doImportFromBundle(type, name, inputStream,
compressed);
return buildResponse(uriInfo, repository);
}
/**
* This method works exactly like
* {@link #importFromBundle(UriInfo, String, String, InputStream)}, but this
* method returns an html content-type. The method exists only for a
* workaround of the javascript ui extjs. This method requires admin
* privileges.<br />
*
* Status codes:
* <ul>
* <li>201 created</li>
* <li>400 bad request, the import bundle feature is not supported by this
* type of repositories or the parameters are not valid.</li>
* <li>500 internal server error</li>
* <li>409 conflict, a repository with the name already exists.</li>
* </ul>
*
*
* @param type
* @param type repository type
* @param name name of the repository
* @param inputStream input bundle
* @param compressed true if the bundle is gzip compressed
*
* @return
* @return empty response with location header which points to the imported
* repository
* @since 1.43
*/
@POST
@Path("{type}/bundle.html")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_HTML)
public Response importFromBundleUI(@PathParam("type") String type,
@FormDataParam("name") String name,
@FormDataParam("bundle") InputStream inputStream, @QueryParam("compressed")
@DefaultValue("false") boolean compressed)
{
Response response;
try
{
doImportFromBundle(type, name, inputStream, compressed);
response = Response.ok(new RestActionUploadResult(true)).build();
}
catch (WebApplicationException ex)
{
logger.warn("error durring bundle import", ex);
response = Response.fromResponse(ex.getResponse()).entity(
new RestActionUploadResult(false)).build();
}
return response;
}
/**
* Imports a external repository which is accessible via url. The method can
* only be used, if the repository type supports the {@link Command#PULL}. The
* method will return a location header with the url to the imported
* repository. This method requires admin privileges.<br />
*
* Status codes:
* <ul>
* <li>201 created</li>
* <li>400 bad request, the import by url feature is not supported by this
* type of repositories or the parameters are not valid.</li>
* <li>409 conflict, a repository with the name already exists.</li>
* <li>500 internal server error</li>
* </ul>
*
* @param uriInfo uri info
* @param type repository type
* @param request request object
*
* @return empty response with location header which points to the imported
* repository
* @since 1.43
*/
@POST
@Path("{type}/url")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response importFromUrl(@Context UriInfo uriInfo,
@PathParam("type") String type, UrlImportRequest request)
{
SecurityUtils.getSubject().checkRole(Role.ADMIN);
checkNotNull(request, "request is required");
checkArgument(!Strings.isNullOrEmpty(request.getName()),
"request does not contain name of the repository");
checkArgument(!Strings.isNullOrEmpty(request.getUrl()),
"request does not contain url of the remote repository");
Type t = type(type);
checkSupport(t, Command.PULL, request);
logger.info("start {} import for external url {}", type, request.getUrl());
Repository repository = create(type, request.getName());
RepositoryService service = null;
try
{
service = serviceFactory.create(repository);
service.getPullCommand().pull(request.getUrl());
}
catch (RepositoryException ex)
{
handleImportFailure(ex, repository);
}
catch (IOException ex)
{
handleImportFailure(ex, repository);
}
finally
{
IOUtil.close(service);
}
return buildResponse(uriInfo, repository);
}
/**
* Imports repositories of the given type from the configured repository
* directory. This method requires admin privileges.<br />
* <br />
* Status codes:
* <ul>
* <li>200 ok, successful</li>
* <li>400 bad request, the import feature is not
* supported by this type of repositories.</li>
* <li>500 internal server error</li>
* </ul>
*
* @param type repository type
*
* @return imported repositories
*/
@POST
@Path("{type}")
@TypeHint(Repository[].class)
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public GenericEntity<List<Repository>> importRepositories(
@PathParam("type") String type)
public Response importRepositories(@PathParam("type") String type)
{
SecurityUtil.assertIsAdmin();
SecurityUtils.getSubject().checkRole(Role.ADMIN);
List<Repository> repositories = new ArrayList<Repository>();
importFromDirectory(repositories, type);
//J-
return Response.ok(
new GenericEntity<List<Repository>>(repositories) {}
).build();
//J+
}
/**
* Imports repositories of all supported types from the configured repository
* directories. This method requires admin privileges.<br />
* <br />
* Status codes:
* <ul>
* <li>200 ok, successful</li>
* <li>400 bad request, the import feature is not
* supported by this type of repositories.</li>
* <li>500 internal server error</li>
* </ul>
*
* @return imported repositories
*/
@POST
@TypeHint(Repository[].class)
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response importRepositories()
{
SecurityUtils.getSubject().checkRole(Role.ADMIN);
logger.info("start directory import for all supported repository types");
List<Repository> repositories = new ArrayList<Repository>();
for (Type t : findImportableTypes())
{
importFromDirectory(repositories, t.getName());
}
//J-
return Response.ok(
new GenericEntity<List<Repository>>(repositories) {}
).build();
//J+
}
/**
* Imports repositories of the given type from the configured repository
* directory. Returns a list of successfully imported directories and a list
* of failed directories. This method requires admin privileges.<br />
* <br />
* Status codes:
* <ul>
* <li>200 ok, successful</li>
* <li>400 bad request, the import feature is not
* supported by this type of repositories.</li>
* <li>500 internal server error</li>
* </ul>
*
* @param type repository type
*
* @return imported repositories
* @since 1.43
*/
@POST
@Path("{type}/directory")
@TypeHint(ImportResult.class)
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response importRepositoriesFromDirectory(
@PathParam("type") String type)
{
SecurityUtils.getSubject().checkRole(Role.ADMIN);
Response response;
RepositoryHandler handler = manager.getHandler(type);
if (handler != null)
{
logger.info("start directory import for repository type {}", type);
try
{
List<String> repositoryNames =
handler.getImportHandler().importRepositories(manager);
ImportResult result;
ImportHandler importHandler = handler.getImportHandler();
if (repositoryNames != null)
if (importHandler instanceof AdvancedImportHandler)
{
for (String repositoryName : repositoryNames)
{
Repository repository = manager.get(type, repositoryName);
if (repository != null)
{
repositories.add(repository);
}
else if (logger.isWarnEnabled())
{
logger.warn("could not find imported repository {}",
repositoryName);
}
}
logger.debug("start directory import, using advanced import handler");
result =
((AdvancedImportHandler) importHandler)
.importRepositoriesFromDirectory(manager);
}
else
{
logger.debug("start directory import, using normal import handler");
result = new ImportResult(importHandler.importRepositories(manager),
ImmutableList.<String>of());
}
response = Response.ok(result).build();
}
catch (Exception ex)
catch (NotSupportedFeatuerException ex)
{
throw new WebApplicationException(ex);
logger
.warn(
"import feature is not supported by repository handler for type "
.concat(type), ex);
response = Response.status(Status.BAD_REQUEST).build();
}
catch (IOException ex)
{
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 if (logger.isWarnEnabled())
else
{
logger.warn("could not find handler for type {}", type);
logger.warn("could not find reposiotry handler for type {}", type);
response = Response.status(Status.BAD_REQUEST).build();
}
return new GenericEntity<List<Repository>>(repositories) {}
;
return response;
}
//~--- get methods ----------------------------------------------------------
/**
* Returns a list of repository types, which support the directory import
* feature.
*
* This method requires admin privileges.<br />
* <br />
* Status codes:
* <ul>
* <li>200 ok, successful</li>
* <li>400 bad request, the import feature is not
* supported by this type of repositories.</li>
* <li>500 internal server error</li>
* </ul>
*
* @return list of repository types
*/
@GET
@TypeHint(Type[].class)
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response getImportableTypes()
{
SecurityUtils.getSubject().checkRole(Role.ADMIN);
List<Type> types = findImportableTypes();
//J-
return Response.ok(
new GenericEntity<List<Type>>(types) {}
).build();
//J+
}
//~--- methods --------------------------------------------------------------
/**
* Build rest response for repository.
*
*
* @param uriInfo uri info
* @param repository imported repository
*
* @return rest response
*/
private Response buildResponse(UriInfo uriInfo, Repository repository)
{
URI location = uriInfo.getBaseUriBuilder().path(
RepositoryResource.class).path(repository.getId()).build();
return Response.created(location).build();
}
/**
* Check repository type for support for the given command.
*
*
* @param type repository type
* @param cmd command
* @param request request object
*/
private void checkSupport(Type type, Command cmd, Object request)
{
if (!(type instanceof RepositoryType))
{
logger.warn("type {} is not a repository type", type.getName());
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
Set<Command> cmds = ((RepositoryType) type).getSupportedCommands();
if (!cmds.contains(cmd))
{
logger.warn("type {} does not support this type of import: {}",
type.getName(), request);
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
}
/**
* Creates a new repository with the given name and type.
*
*
* @param type repository type
* @param name repository name
*
* @return newly created repository
*/
private Repository create(String type, String name)
{
Repository repository = null;
try
{
repository = new Repository(null, type, name);
manager.create(repository);
}
catch (RepositoryAllreadyExistExeption ex)
{
logger.warn("a {} repository with the name {} already exists", ex);
throw new WebApplicationException(Response.Status.CONFLICT);
}
catch (RepositoryException ex)
{
handleGenericCreationFailure(ex, type, name);
}
catch (IOException ex)
{
handleGenericCreationFailure(ex, type, name);
}
return repository;
}
/**
* Start bundle import.
*
*
* @param type repository type
* @param name name of the repository
* @param inputStream bundle stream
* @param compressed true if the bundle is gzip compressed
*
* @return imported repository
*/
private Repository doImportFromBundle(String type, String name,
InputStream inputStream, boolean compressed)
{
SecurityUtils.getSubject().checkRole(Role.ADMIN);
checkArgument(!Strings.isNullOrEmpty(name),
"request does not contain name of the repository");
checkNotNull(inputStream, "bundle inputStream is required");
Repository repository;
try
{
Type t = type(type);
checkSupport(t, Command.UNBUNDLE, "bundle");
repository = create(type, name);
RepositoryService service = null;
File file = File.createTempFile("scm-import-", ".bundle");
try
{
long length = Files.asByteSink(file).writeFrom(inputStream);
logger.info("copied {} bytes to temp, start bundle import", length);
service = serviceFactory.create(repository);
service.getUnbundleCommand().setCompressed(compressed).unbundle(file);
}
catch (RepositoryException ex)
{
handleImportFailure(ex, repository);
}
catch (IOException ex)
{
handleImportFailure(ex, repository);
}
finally
{
IOUtil.close(service);
IOUtil.delete(file);
}
}
catch (IOException ex)
{
logger.warn("could not create temporary file", ex);
throw new WebApplicationException(ex);
}
return repository;
}
/**
* Method description
*
*
* @return
*/
@GET
@TypeHint(Type[].class)
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public GenericEntity<List<Type>> getImportableTypes()
private List<Type> findImportableTypes()
{
SecurityUtil.assertIsAdmin();
List<Type> types = new ArrayList<Type>();
Collection<Type> handlerTypes = manager.getTypes();
@@ -208,12 +678,217 @@ public class RepositoryImportResource
}
}
return new GenericEntity<List<Type>>(types) {}
;
return types;
}
/**
* Handle creation failures.
*
*
* @param ex exception
* @param type repository type
* @param name name of the repository
*/
private void handleGenericCreationFailure(Exception ex, String type,
String name)
{
logger.error(String.format("could not create repository {} with type {}",
type, name), ex);
throw new WebApplicationException(ex);
}
/**
* Handle import failures.
*
*
* @param ex exception
* @param repository repository
*/
private void handleImportFailure(Exception ex, Repository repository)
{
logger.error("import for repository failed, delete repository", ex);
try
{
manager.delete(repository);
}
catch (IOException e)
{
logger.error("can not delete repository", e);
}
catch (RepositoryException e)
{
logger.error("can not delete repository", e);
}
throw new WebApplicationException(ex,
Response.Status.INTERNAL_SERVER_ERROR);
}
/**
* Import repositories from a specific type.
*
*
* @param repositories repository list
* @param type type of repository
*/
private void importFromDirectory(List<Repository> repositories, String type)
{
RepositoryHandler handler = manager.getHandler(type);
if (handler != null)
{
logger.info("start directory import for repository type {}", type);
try
{
List<String> repositoryNames =
handler.getImportHandler().importRepositories(manager);
if (repositoryNames != null)
{
for (String repositoryName : repositoryNames)
{
Repository repository = manager.get(type, repositoryName);
if (repository != null)
{
repositories.add(repository);
}
else if (logger.isWarnEnabled())
{
logger.warn("could not find imported repository {}",
repositoryName);
}
}
}
}
catch (NotSupportedFeatuerException ex)
{
throw new WebApplicationException(ex, Response.Status.BAD_REQUEST);
}
catch (IOException ex)
{
throw new WebApplicationException(ex);
}
catch (RepositoryException ex)
{
throw new WebApplicationException(ex);
}
}
else
{
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
}
/**
* Method description
*
*
* @param type
*
* @return
*/
private Type type(String type)
{
RepositoryHandler handler = manager.getHandler(type);
if (handler == null)
{
logger.warn("no handler for type {} found", type);
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return handler.getType();
}
//~--- inner classes --------------------------------------------------------
/**
* Request for importing external repositories which are accessible via url.
*/
@XmlRootElement(name = "import")
@XmlAccessorType(XmlAccessType.FIELD)
public static class UrlImportRequest
{
/**
* Constructs ...
*
*/
public UrlImportRequest() {}
/**
* Constructs a new {@link UrlImportRequest}
*
*
* @param name name of the repository
* @param url external url of the repository
*/
public UrlImportRequest(String name, String url)
{
this.name = name;
this.url = url;
}
//~--- methods ------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
//J-
return Objects.toStringHelper(this)
.add("name", name)
.add("url", url)
.toString();
//J+
}
//~--- get methods --------------------------------------------------------
/**
* Returns name of the repository.
*
*
* @return name of the repository
*/
public String getName()
{
return name;
}
/**
* Returns external url of the repository.
*
*
* @return external url of the repository
*/
public String getUrl()
{
return url;
}
//~--- fields -------------------------------------------------------------
/** name of the repository */
private String name;
/** external url of the repository */
private String url;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private RepositoryManager manager;
/** repository manager */
private final RepositoryManager manager;
/** repository service factory */
private final RepositoryServiceFactory serviceFactory;
}

View File

@@ -99,6 +99,10 @@ public class DefaultUserManager extends AbstractUserManager
/**
* Constructs ...
*
<<<<<<< mine
=======
*
>>>>>>> theirs
* @param userDAO
*/
@Inject
@@ -227,8 +231,8 @@ public class DefaultUserManager extends AbstractUserManager
public void init(SCMContextProvider context)
{
// TODO improve
if (!userDAO.contains("scmadmin") &&!userDAO.contains("anonymous"))
// create default account only, if no other account is available
if (userDAO.getAll().isEmpty())
{
createDefaultAccounts();
}
@@ -519,6 +523,8 @@ public class DefaultUserManager extends AbstractUserManager
{
try
{
logger.info("create default accounts");
JAXBContext context = JAXBContext.newInstance(User.class);
Unmarshaller unmarshaller = context.createUnmarshaller();

View File

@@ -42,6 +42,10 @@
<listener>
<listener-class>sonia.scm.boot.BootstrapContextListener</listener-class>
</listener>
<listener>
<listener-class>sonia.scm.HttpSessionListenerHolder</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>

View File

@@ -77,6 +77,7 @@
<!-- sonia.util -->
<script type="text/javascript" src="resources/js/util/sonia.util.js"></script>
<script type="text/javascript" src="resources/js/util/sonia.util.link.js"></script>
<script type="text/javascript" src="resources/js/util/sonia.util.tip.js"></script>
<!-- sonia.override -->
<script type="text/javascript" src="resources/js/override/ext.form.vtypes.js"></script>

View File

@@ -105,6 +105,11 @@ a.scm-link:hover {
margin-left: 2px;
}
.scm-form-fileupload-help-button {
position: absolute;
right: -19px;
}
.scm-nav-item {
cursor: pointer;
}
@@ -238,3 +243,12 @@ div.noscript-container h1 {
.unhealthy {
color: red;
}
/** import **/
.import-fu {
margin-right: 24px;
}
.import-fu input {
width: 215px;
}

View File

@@ -77,6 +77,9 @@ Ext.override(Ext.form.Field, {
cls = 'scm-form-combo-help-button';
}
break;
case 'fileuploadfield':
cls = 'scm-form-fileupload-help-button';
break;
case 'textarea':
cls = 'scm-form-textarea-help-button';
break;

View File

@@ -31,75 +31,517 @@
Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{
titleText: 'Import Repositories',
okText: 'Ok',
closeText: 'Close',
// cache
importForm: null,
imported: [],
importJobsFinished: 0,
importJobs: 0,
title: 'Repository Import Wizard',
initComponent: function(){
this.imported = [];
this.importJobsFinished = 0;
this.importJobs = 0;
this.addEvents('finish');
var config = {
layout:'fit',
width:300,
height:170,
title: this.title,
layout: 'fit',
width: 420,
height: 190,
closable: true,
resizable: false,
resizable: true,
plain: true,
border: false,
modal: true,
title: this.titleText,
bodyCssClass: 'x-panel-mc',
items: [{
id: 'importRepositoryForm',
frame: true,
xtype: 'form',
defaultType: 'checkbox'
}],
buttons: [{
id: 'startRepositoryImportButton',
text: this.okText,
formBind: true,
scope: this,
handler: this.importRepositories
},{
text: this.closeText,
scope: this,
handler: this.close
}],
listeners: {
afterrender: {
fn: this.readImportableTypes,
scope: this
id: 'scmRepositoryImportWizard',
xtype: 'scmRepositoryImportWizard',
listeners: {
finish: {
fn: this.onFinish,
scope: this
}
}
}
}]
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.repository.ImportWindow.superclass.initComponent.apply(this, arguments);
},
readImportableTypes: function(){
if (debug){
console.debug('read importable types');
onFinish: function(config){
this.fireEvent('finish', config);
this.close();
}
});
Sonia.repository.ImportPanel = Ext.extend(Ext.Panel, {
// text
backText: 'Back',
nextText: 'Next',
closeBtnText: 'Close',
imported: [],
failed: [],
// help text
importTypeDirectoryHelpText: 'Imports all repositories that are located at the repository folder of SCM-Manager.',
importTypeURLHelpText: 'Imports a repository from a remote url.',
importTypeFileHelpText: 'Imports a repository from a file (e.g. SVN dump).',
importUrlNameHelpText: 'The name of the repository in SCM-Manager.',
importUrlHelpText: 'The source url of the repository.',
importFileNameHelpText: 'The name of the repository in SCM-Manager.',
importFileHelpText: 'Choose the dump file you want to import to SCM-Manager.',
importFileGZipCompressedHelpText: 'The file is gzip compressed.',
// tips
tipRepositoryType: 'Choose your repository type for the import.',
tipImportType: 'Select the type of import. <b>Note:</b> Not all repository types support all options.',
// cache
nextButton: null,
prevButton: null,
// settings
repositoryType: null,
// active card
activeForm: null,
// result template
tpl: new Ext.XTemplate([
'<p style="padding: 0 10px">',
' <tpl if="imported">',
' <b>Imported repositories</b><br />',
' <tpl for="imported">',
' - {.}<br />',
' </tpl>',
' <br />',
' </tpl>',
' <tpl if="failed">',
' <b>Failed to import the following directories</b><br />',
' <tpl for="failed">',
' - {.}<br />',
' </tpl>',
' </tpl>',
' <tpl if="isEmpty(imported, failed)">',
' <b>No repositories to import</b>',
' </tpl>',
'</p>',
'<br />'
]),
initComponent: function(){
this.addEvents('finish');
// fix initialization bug
this.imported = [];
this.failed = [];
this.activeForm = null;
var typeItems = [];
Ext.each(state.repositoryTypes, function(repositoryType){
typeItems.push({
boxLabel: repositoryType.displayName,
name: 'repositoryType',
inputValue: repositoryType.name,
checked: false
});
});
typeItems = typeItems.sort(function(a, b){
return a.boxLabel > b.boxLabel;
});
typeItems.push({
xtype: 'scmTip',
content: this.tipRepositoryType,
width: '100%'
});
var config = {
layout: 'card',
activeItem: 0,
bodyStyle: 'padding: 5px',
defaults: {
bodyCssClass: 'x-panel-mc',
border: false,
labelWidth: 120,
width: 250
},
bbar: ['->',{
id: 'move-prev',
text: this.backText,
handler: this.navHandler.createDelegate(this, [-1]),
disabled: true,
scope: this
},{
id: 'move-next',
text: this.nextText,
handler: this.navHandler.createDelegate(this, [1]),
disabled: true,
scope: this
},{
id: 'closeBtn',
text: this.closeBtnText,
handler: this.applyChanges,
disabled: true,
scope: this
}],
items: [{
id: 'repositoryTypeLayout',
items: [{
id: 'chooseRepositoryType',
xtype: 'radiogroup',
name: 'chooseRepositoryType',
columns: 1,
items: [typeItems],
listeners: {
change: {
fn: function(){
this.getNextButton().setDisabled(false);
},
scope: this
}
}
}]
},{
id: 'importTypeLayout',
items: [{
id: 'chooseImportType',
xtype: 'radiogroup',
name: 'chooseImportType',
columns: 1,
items: [{
id: 'importTypeDirectory',
boxLabel: 'Import from directory',
name: 'importType',
inputValue: 'directory',
disabled: false,
helpText: this.importTypeDirectoryHelpText
},{
id: 'importTypeURL',
boxLabel: 'Import from url',
name: 'importType',
inputValue: 'url',
checked: false,
disabled: true,
helpText: this.importTypeURLHelpText
},{
id: 'importTypeFile',
boxLabel: 'Import from file',
name: 'importType',
inputValue: 'file',
checked: false,
disabled: true,
helpText: this.importTypeFileHelpText
},{
xtype: 'scmTip',
content: this.tipImportType,
width: '100%'
}],
listeners: {
change: {
fn: function(){
this.getNextButton().setDisabled(false);
},
scope: this
}
}
}]
},{
id: 'importUrlLayout',
xtype: 'form',
monitorValid: true,
defaults: {
width: 250
},
listeners: {
clientvalidation: {
fn: this.urlFormValidityMonitor,
scope: this
}
},
items: [{
id: 'importUrlName',
xtype: 'textfield',
fieldLabel: 'Repository name',
name: 'name',
allowBlank: false,
vtype: 'repositoryName',
helpText: this.importUrlNameHelpText
},{
id: 'importUrl',
xtype: 'textfield',
fieldLabel: 'Import URL',
name: 'url',
allowBlank: false,
vtype: 'url',
helpText: this.importUrlHelpText
},{
xtype: 'scmTip',
content: 'Please insert name and remote url of the repository.',
width: '100%'
}]
},{
id: 'importFileLayout',
xtype: 'form',
fileUpload: true,
monitorValid: true,
listeners: {
clientvalidation: {
fn: this.fileFormValidityMonitor,
scope: this
}
},
items: [{
id: 'importFileName',
xtype: 'textfield',
fieldLabel: 'Repository name',
name: 'name',
type: 'textfield',
width: 250,
allowBlank: false,
vtype: 'repositoryName',
helpText: this.importFileNameHelpText
},{
id: 'importFile',
xtype: 'fileuploadfield',
fieldLabel: 'Import File',
ctCls: 'import-fu',
name: 'bundle',
buttonText: '',
allowBlank: false,
helpText: this.importFileHelpText,
cls: 'import-fu',
buttonCfg: {
iconCls: 'upload-icon'
}
},{
id: 'importFileGZipCompressed',
xtype: 'checkbox',
fieldLabel: 'GZip compressed',
helpText: this.importFileGZipCompressedHelpText
},{
xtype: 'scmTip',
content: 'Please insert name and upload the repository file.',
width: '100%'
}]
},{
id: 'importFinishedLayout',
layout: 'form',
defaults: {
width: 250
},
items: [{
id: 'resultPanel',
xtype: 'panel',
bodyCssClass: 'x-panel-mc',
tpl: this.tpl
},{
id: 'finalTip',
xtype: 'scmTip',
content: 'Please see log for details.',
width: '100%',
hidden: true
}]
}]
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.repository.ImportPanel.superclass.initComponent.apply(this, arguments);
},
navHandler: function(direction){
this.activeForm = null;
var layout = this.getLayout();
var id = layout.activeItem.id;
var next = -1;
if ( id === 'repositoryTypeLayout' && direction === 1 ){
this.repositoryType = Ext.getCmp('chooseRepositoryType').getValue().getRawValue();
this.enableAvailableImportTypes();
next = 1;
}
else if ( id === 'importTypeLayout' && direction === -1 ){
next = 0;
Ext.getCmp('move-prev').setDisabled(true);
Ext.getCmp('move-next').setDisabled(false);
}
else if ( id === 'importTypeLayout' && direction === 1 ){
Ext.getCmp('move-next').setDisabled(false);
var v = Ext.getCmp('chooseImportType').getValue();
if ( v ){
switch (v.getRawValue()){
case 'directory':
this.importFromDirectory(layout);
break;
case 'url':
next = 2;
this.activeForm = 'url';
break;
case 'file':
next = 3;
this.activeForm = 'file';
break;
}
}
}
else if ( (id === 'importUrlLayout' || id === 'importFileLayout') && direction === -1 )
{
next = 1;
}
else if ( id === 'importUrlLayout' && direction === 1 )
{
this.importFromUrl(layout, Ext.getCmp('importUrlLayout').getForm().getValues());
}
else if ( id === 'importFileLayout' && direction === 1 )
{
this.importFromFile(layout, Ext.getCmp('importFileLayout').getForm());
}
if ( next >= 0 ){
layout.setActiveItem(next);
}
},
getNextButton: function(){
if (!this.nextButton){
this.nextButton = Ext.getCmp('move-next');
}
return this.nextButton;
},
getPrevButton: function(){
if (!this.prevButton){
this.prevButton = Ext.getCmp('move-prev');
}
return this.prevButton;
},
showLoadingBox: function(){
return Ext.MessageBox.show({
title: 'Loading',
msg: 'Import repository',
width: 300,
wait: true,
animate: true,
progress: true,
closable: false
});
},
urlFormValidityMonitor: function(form, valid){
if (this.activeForm === 'url'){
this.formValidityMonitor(valid);
}
},
fileFormValidityMonitor: function(form, valid){
if (this.activeForm === 'file'){
this.formValidityMonitor(valid);
}
},
formValidityMonitor: function(valid){
var nbt = this.getNextButton();
if (valid && nbt.disabled){
nbt.setDisabled(false);
} else if (!valid && !nbt.disabled){
nbt.setDisabled(true);
}
},
appendImportResult: function(layout, result){
for (var i=0; i<result.importedDirectories.length; i++){
this.imported.push(result.importedDirectories[i]);
}
for (var i=0; i<result.failedDirectories.length; i++){
this.failed.push(result.failedDirectories[i]);
}
this.displayResult(layout);
},
appendImported: function(layout, name){
this.imported.push(name);
this.displayResult(layout);
},
appendFailed: function(layout, name){
this.failed.push(name);
this.displayResult(layout);
layout.setActiveItem(4);
},
displayResult: function(layout){
this.getNextButton().setDisabled(true);
this.getPrevButton().setDisabled(true);
Ext.getCmp('closeBtn').setDisabled(false);
var model = {
imported: this.imported.length > 0 ? this.imported : null,
failed: this.failed.length > 0 ? this.failed : null,
isEmpty: function(imported, failed){
return !imported && !failed;
}
};
var resultPanel = Ext.getCmp('resultPanel');
Ext.getCmp('finalTip').setVisible(this.failed.length > 0);
resultPanel.tpl.overwrite(resultPanel.body, model);
layout.setActiveItem(4);
},
importFromFile: function(layout, form){
var compressed = Ext.getCmp('importFileGZipCompressed').getValue();
var lbox = this.showLoadingBox();
form.submit({
url: restUrl + 'import/repositories/' + this.repositoryType + '/bundle.html?compressed=' + compressed,
timeout: 300000, // 5min
scope: this,
success: function(form){
lbox.hide();
this.appendImported(layout, form.getValues().name);
},
failure: function(form){
lbox.hide();
this.appendFailed(layout, form.getValues().name);
}
});
},
importFromUrl: function(layout, repository){
var lbox = this.showLoadingBox();
Ext.Ajax.request({
url: restUrl + 'import/repositories.json',
method: 'GET',
url: restUrl + 'import/repositories/' + this.repositoryType + '/url.json',
method: 'POST',
scope: this,
timeout: 300000, // 5min
jsonData: repository,
success: function(){
lbox.hide();
this.appendImported(layout, repository.name);
},
failure: function(){
lbox.hide();
this.appendFailed(layout, repository.name);
}
});
},
importFromDirectory: function(layout){
var lbox = this.showLoadingBox();
Ext.Ajax.request({
url: restUrl + 'import/repositories/' + this.repositoryType + '/directory.json',
method: 'POST',
scope: this,
success: function(response){
var obj = Ext.decode(response.responseText);
this.renderTypeCheckboxes(obj);
this.doLayout();
lbox.hide();
this.appendImportResult(layout, Ext.decode(response.responseText));
},
failure: function(result){
lbox.hide();
main.handleRestFailure(
result,
this.errorTitleText,
@@ -107,123 +549,34 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{
);
}
});
},
renderTypeCheckboxes: function(types){
Ext.each(types, function(type){
this.renderCheckbox(type);
}, this);
},
getImportForm: function(){
if (!this.importForm){
this.importForm = Ext.getCmp('importRepositoryForm');
}
return this.importForm;
},
renderCheckbox: function(type){
this.getImportForm().add({
xtype: 'checkbox',
name: 'type',
fieldLabel: type.displayName,
inputValue: type.name
});
},
importRepositories: function(){
if (debug){
console.debug('start import of repositories');
}
var form = this.getImportForm().getForm();
var values = form.getValues().type;
if ( values ){
if ( Ext.isArray(values) ){
this.importJobs = values.length;
} else {
this.importJobs = 1;
enableAvailableImportTypes: function(){
var type = null;
Ext.each(state.repositoryTypes, function(repositoryType){
if (repositoryType.name === this.repositoryType){
type = repositoryType;
}
} else {
this.importJobs = 0;
}
Ext.each(values, function(value){
this.importRepositoriesOfType(value);
}, this);
},
appendImported: function(repositories){
for (var i=0; i<repositories.length; i++){
this.imported.push(repositories[i]);
}
this.importJobsFinished++;
if ( this.importJobsFinished >= this.importJobs ){
if (debug){
console.debug( 'import of ' + this.importJobsFinished + ' jobs finished' );
}
this.printImported();
if ( type !== null ){
Ext.getCmp('chooseImportType').setValue(null);
this.getNextButton().setDisabled(true);
this.getPrevButton().setDisabled(false);
Ext.getCmp('importTypeURL').setDisabled(type.supportedCommands.indexOf('PULL') < 0);
Ext.getCmp('importTypeFile').setDisabled(type.supportedCommands.indexOf('UNBUNDLE') < 0);
}
},
printImported: function(){
var store = new Ext.data.JsonStore({
fields: ['type', 'name']
});
store.loadData(this.imported);
var colModel = new Ext.grid.ColumnModel({
defaults: {
sortable: true,
scope: this
},
columns: [
{id: 'name', header: 'Name', dataIndex: 'name'},
{id: 'type', header: 'Type', dataIndex: 'type'}
]
});
this.getImportForm().add({
xtype: 'grid',
autoExpandColumn: 'name',
store: store,
colModel: colModel,
height: 100
});
var h = this.getHeight();
this.setHeight( h + 100 );
this.doLayout();
// reload repositories panel
applyChanges: function(){
var panel = Ext.getCmp('repositories');
if (panel){
panel.getGrid().reload();
}
},
importRepositoriesOfType: function(type){
if (debug){
console.debug('start import of ' + type + ' repositories');
}
var b = Ext.getCmp('startRepositoryImportButton');
if ( b ){
b.setDisabled(true);
}
Ext.Ajax.request({
url: restUrl + 'import/repositories/' + type + '.json',
method: 'POST',
scope: this,
success: function(response){
var obj = Ext.decode(response.responseText);
this.appendImported(obj);
},
failure: function(result){
main.handleRestFailure(
result,
this.errorTitleText,
this.errorMsgText
);
}
});
this.fireEvent('finish');
}
});
});
// register xtype
Ext.reg('scmRepositoryImportWizard', Sonia.repository.ImportPanel);

View File

@@ -0,0 +1,77 @@
/*
* 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
*
*/
Sonia.util.Tip = Ext.extend(Ext.BoxComponent, {
tpl: new Ext.XTemplate('\
<div id="" class="x-tip" style="position: inherit; visibility: visible;">\
<div class="x-tip-tl">\
<div class="x-tip-tr">\
<div class="x-tip-tc">\
<div class="x-tip-header x-unselectable">\
<span class="x-tip-header-text"></span>\
</div>\
</div>\
</div>\
</div>\
<div class="x-tip-bwrap">\
<div class="x-tip-ml">\
<div class="x-tip-mr">\
<div class="x-tip-mc">\
<div class="x-tip-body" style="height: auto; width: auto;">\
{content}\
</div>\
</div>\
</div>\
</div>\
<div class="x-tip-bl x-panel-nofooter">\
<div class="x-tip-br">\
<div class="x-tip-bc"></div>\
</div>\
</div>\
</div>\
</div>'),
constructor: function(config) {
config = config || {};
var cl = 'scm-tip';
if (config['class']){
cl += ' ' + config['class'];
}
config.xtype = 'box';
this.html = this.tpl.apply({content: config.content});
Sonia.util.Tip.superclass.constructor.apply(this, arguments);
}
});
// register xtype
Ext.reg('scmTip', Sonia.util.Tip);

View File

@@ -35,13 +35,22 @@ package sonia.scm.user;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import sonia.scm.store.JAXBStoreFactory;
import sonia.scm.store.StoreFactory;
import sonia.scm.user.xml.XmlUserDAO;
import sonia.scm.util.MockUtil;
import org.junit.Before;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
import java.util.Collections;
import java.util.List;
/**
*
@@ -49,11 +58,6 @@ import org.junit.Before;
*/
public class DefaultUserManagerTest extends UserManagerTestBase
{
@Before
public void setAdminSubject(){
setSubject(MockUtil.createAdminSubject());
}
/**
* Method description
@@ -63,13 +67,72 @@ public class DefaultUserManagerTest extends UserManagerTestBase
*/
@Override
public UserManager createManager()
{
return new DefaultUserManager(createXmlUserDAO());
}
/**
* Method description
*
*/
@Test
public void testDefaultAccountAfterFristStart()
{
UserDAO userDAO = mock(UserDAO.class);
List<User> users = Lists.newArrayList(new User("tuser"));
when(userDAO.getAll()).thenReturn(users);
UserManager userManager = new DefaultUserManager(userDAO);
userManager.init(contextProvider);
verify(userDAO, never()).add(any(User.class));
}
/**
* Method description
*
*/
@Test
@SuppressWarnings("unchecked")
public void testDefaultAccountCreation()
{
UserDAO userDAO = mock(UserDAO.class);
when(userDAO.getAll()).thenReturn(Collections.EMPTY_LIST);
UserManager userManager = new DefaultUserManager(userDAO);
userManager.init(contextProvider);
verify(userDAO, times(2)).add(any(User.class));
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*/
@Before
public void setAdminSubject()
{
setSubject(MockUtil.createAdminSubject());
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
private XmlUserDAO createXmlUserDAO()
{
StoreFactory factory = new JAXBStoreFactory();
factory.init(contextProvider);
XmlUserDAO userDAO = new XmlUserDAO(factory);
return new DefaultUserManager(userDAO);
return new XmlUserDAO(factory);
}
}