diff --git a/pom.xml b/pom.xml index b783260da0..34a80f50fa 100644 --- a/pom.xml +++ b/pom.xml @@ -421,7 +421,7 @@ - 1.9.5 + 1.10.8 1.3 4.11 @@ -430,8 +430,7 @@ 1.1.2 2.5 3.0 - 1.18.1 - 2.3.20 + 1.18.2 1.2.0 @@ -445,7 +444,7 @@ 3.4.1.201406201815-r 1.8.5-scm2 - + 16.0.1 diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml index 5df700628d..853dca7d93 100644 --- a/scm-clients/scm-cli-client/pom.xml +++ b/scm-clients/scm-cli-client/pom.xml @@ -40,7 +40,7 @@ args4j args4j - 2.0.28 + 2.0.29 @@ -52,7 +52,7 @@ org.freemarker freemarker - ${freemarker.version} + 2.3.21 diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportBundleSubCommand.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportBundleSubCommand.java new file mode 100644 index 0000000000..d746df0828 --- /dev/null +++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportBundleSubCommand.java @@ -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; +} diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportDirectorySubCommand.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportDirectorySubCommand.java new file mode 100644 index 0000000000..788eef4fae --- /dev/null +++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportDirectorySubCommand.java @@ -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 env = Maps.newHashMap(); + + env.put("importedDirectories", wrapper.getImportedDirectories()); + env.put("failedDirectories", wrapper.getFailedDirectories()); + renderTemplate(env, TEMPLATE); + } +} diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportSubCommand.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportSubCommand.java new file mode 100644 index 0000000000..7c8efe704d --- /dev/null +++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportSubCommand.java @@ -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 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; +} diff --git a/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportUrlSubCommand.java b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportUrlSubCommand.java new file mode 100644 index 0000000000..912a9a5fbc --- /dev/null +++ b/scm-clients/scm-cli-client/src/main/java/sonia/scm/cli/cmd/ImportUrlSubCommand.java @@ -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; +} diff --git a/scm-clients/scm-cli-client/src/main/resources/META-INF/services/sonia.scm.cli.cmd.SubCommand b/scm-clients/scm-cli-client/src/main/resources/META-INF/services/sonia.scm.cli.cmd.SubCommand index b4d344adae..91f2345948 100644 --- a/scm-clients/scm-cli-client/src/main/resources/META-INF/services/sonia.scm.cli.cmd.SubCommand +++ b/scm-clients/scm-cli-client/src/main/resources/META-INF/services/sonia.scm.cli.cmd.SubCommand @@ -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 diff --git a/scm-clients/scm-cli-client/src/main/resources/sonia/resources/i18n.properties b/scm-clients/scm-cli-client/src/main/resources/sonia/resources/i18n.properties index 69d88b2cfe..9d6afd3dda 100644 --- a/scm-clients/scm-cli-client/src/main/resources/sonia/resources/i18n.properties +++ b/scm-clients/scm-cli-client/src/main/resources/sonia/resources/i18n.properties @@ -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 \ No newline at end of file diff --git a/scm-clients/scm-cli-client/src/main/resources/sonia/resources/import-from-directory.ftl b/scm-clients/scm-cli-client/src/main/resources/sonia/resources/import-from-directory.ftl new file mode 100644 index 0000000000..33cf9e083e --- /dev/null +++ b/scm-clients/scm-cli-client/src/main/resources/sonia/resources/import-from-directory.ftl @@ -0,0 +1,9 @@ +Imported repositories: +<#list importedDirectories as imported> +- ${imported} + + +Repositories failed to import: +<#list failedDirectories as failed> +- ${failed} + \ No newline at end of file diff --git a/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportBundleRequest.java b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportBundleRequest.java new file mode 100644 index 0000000000..c6c9e71016 --- /dev/null +++ b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportBundleRequest.java @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.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; +} diff --git a/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportResultWrapper.java b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportResultWrapper.java new file mode 100644 index 0000000000..1417e3b888 --- /dev/null +++ b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportResultWrapper.java @@ -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 getFailedDirectories() + { + List directories = result.getFailedDirectories(); + + if (directories == null) + { + directories = ImmutableList.of(); + } + + return directories; + } + + /** + * Method description + * + * + * @return + */ + public List getImportedDirectories() + { + List directories = result.getImportedDirectories(); + + if (directories == null) + { + directories = ImmutableList.of(); + } + + return directories; + } + + /** + * Method description + * + * + * @return + */ + public Iterable 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 + { + + /** + * 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; +} diff --git a/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportUrlRequest.java b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportUrlRequest.java new file mode 100644 index 0000000000..66969f4a86 --- /dev/null +++ b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/ImportUrlRequest.java @@ -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; +} diff --git a/scm-clients/scm-client-api/src/main/java/sonia/scm/client/RepositoryClientHandler.java b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/RepositoryClientHandler.java index b5ec335642..9c0060aaff 100644 --- a/scm-clients/scm-client-api/src/main/java/sonia/scm/client/RepositoryClientHandler.java +++ b/scm-clients/scm-client-api/src/main/java/sonia/scm/client/RepositoryClientHandler.java @@ -51,6 +51,43 @@ import java.util.Collection; public interface RepositoryClientHandler extends ClientHandler { + /** + * 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 * diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml index 45e7069988..37778cad47 100644 --- a/scm-clients/scm-client-impl/pom.xml +++ b/scm-clients/scm-client-impl/pom.xml @@ -45,6 +45,12 @@ ${jersey.version} + + com.sun.jersey.contribs + jersey-multipart + ${jersey.version} + + diff --git a/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyClientProvider.java b/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyClientProvider.java index 48111de0d8..6ad74d748a 100644 --- a/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyClientProvider.java +++ b/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyClientProvider.java @@ -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()); diff --git a/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyRepositoryClientHandler.java b/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyRepositoryClientHandler.java index f1c204531b..ff611c9aa2 100644 --- a/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyRepositoryClientHandler.java +++ b/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyRepositoryClientHandler.java @@ -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 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()); + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java index 124e712c5b..2da506674d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java @@ -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 importRepositories(RepositoryManager manager) throws IOException, RepositoryException { - List imported = new ArrayList(); - - if (logger.isTraceEnabled()) - { - logger.trace("search for repositories to import"); - } - - List 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 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 + * + * @throws T + */ + private 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 * diff --git a/scm-core/src/main/java/sonia/scm/repository/AdvancedImportHandler.java b/scm-core/src/main/java/sonia/scm/repository/AdvancedImportHandler.java new file mode 100644 index 0000000000..7c1127b4a6 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/AdvancedImportHandler.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.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); +} diff --git a/scm-core/src/main/java/sonia/scm/repository/ImportResult.java b/scm-core/src/main/java/sonia/scm/repository/ImportResult.java new file mode 100644 index 0000000000..a7d242abc4 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/ImportResult.java @@ -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 importedDirectories, + List 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 getFailedDirectories() + { + return failedDirectories; + } + + /** + * Returns list of successfully imported directories. + * + * + * @return list of successfully imported directories + */ + public List 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 importedDirectories = Lists.newArrayList(); + + /** failed directories */ + private final List failedDirectories = Lists.newArrayList(); + } + + + //~--- fields --------------------------------------------------------------- + + /** failed directories */ + private List failedDirectories; + + /** successfully imported directories */ + private List importedDirectories; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/AbstractBundleOrUnbundleCommandResponse.java b/scm-core/src/main/java/sonia/scm/repository/api/AbstractBundleOrUnbundleCommandResponse.java new file mode 100644 index 0000000000..36ae6f89ad --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/AbstractBundleOrUnbundleCommandResponse.java @@ -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; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BundleCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BundleCommandBuilder.java new file mode 100644 index 0000000000..c8ac199e16 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/BundleCommandBuilder.java @@ -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 + * @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() + { + + @Override + public OutputStream getOutput() throws IOException + { + return outputStream; + } + }); + } + + //~--- fields --------------------------------------------------------------- + + /** bundle command implementation */ + private final BundleCommand bundleCommand; + + /** repository */ + private final Repository repository; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BundleResponse.java b/scm-core/src/main/java/sonia/scm/repository/api/BundleResponse.java new file mode 100644 index 0000000000..8cad1edc65 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/BundleResponse.java @@ -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 + * @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); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/Command.java b/scm-core/src/main/java/sonia/scm/repository/api/Command.java index baf228fa94..ccb0d8c2c0 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/Command.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/Command.java @@ -56,5 +56,10 @@ public enum Command /** * @since 1.31 */ - INCOMING, OUTGOING, PUSH, PULL; + INCOMING, OUTGOING, PUSH, PULL, + + /** + * @since 1.43 + */ + BUNDLE, UNBUNDLE; } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/PullCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/PullCommandBuilder.java index 1e170d999e..528c93eef9 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/PullCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/PullCommandBuilder.java @@ -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); diff --git a/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java index 5737997442..fabe938ff7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java @@ -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 */ diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java index 6155174476..5ae4b33c33 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java @@ -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) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/UnbundleCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/UnbundleCommandBuilder.java new file mode 100644 index 0000000000..7860513b51 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/UnbundleCommandBuilder.java @@ -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 + * @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() + { + + @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; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/UnbundleResponse.java b/scm-core/src/main/java/sonia/scm/repository/api/UnbundleResponse.java new file mode 100644 index 0000000000..0c177ba699 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/UnbundleResponse.java @@ -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 + * @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); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BundleCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BundleCommand.java new file mode 100644 index 0000000000..8a0f21a714 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BundleCommand.java @@ -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 + * @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; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BundleCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/BundleCommandRequest.java new file mode 100644 index 0000000000..4315b39621 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BundleCommandRequest.java @@ -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 + * @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; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/PullCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/PullCommandRequest.java index be1305b4db..274e124f5e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/PullCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/PullCommandRequest.java @@ -33,7 +33,8 @@ package sonia.scm.repository.spi; /** - * + * Request object for {@link PullCommand}. + * * @author Sebastian Sdorra * @since 1.31 */ diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/PushCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/PushCommandRequest.java index d1f51e427e..679e93f1bb 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/PushCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/PushCommandRequest.java @@ -33,6 +33,7 @@ package sonia.scm.repository.spi; /** + * Request object for {@link PushCommand}. * * @author Sebastian Sdorra * @since 1.31 diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/RemoteCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/RemoteCommandRequest.java index 949b56efa7..0f3cd909d9 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/RemoteCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/RemoteCommandRequest.java @@ -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; } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java index 42dd6a2b15..976f38fffb 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java @@ -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); + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/UnbundleCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/UnbundleCommand.java new file mode 100644 index 0000000000..4777e0c11b --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/UnbundleCommand.java @@ -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 + * @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; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/UnbundleCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/UnbundleCommandRequest.java new file mode 100644 index 0000000000..b9e9a5182b --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/UnbundleCommandRequest.java @@ -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 + * @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; +} diff --git a/scm-core/src/main/java/sonia/scm/url/RestUrlProvider.java b/scm-core/src/main/java/sonia/scm/url/RestUrlProvider.java index cb0b3de82d..50f5912edb 100644 --- a/scm-core/src/main/java/sonia/scm/url/RestUrlProvider.java +++ b/scm-core/src/main/java/sonia/scm/url/RestUrlProvider.java @@ -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 * diff --git a/scm-core/src/main/java/sonia/scm/url/UrlProvider.java b/scm-core/src/main/java/sonia/scm/url/UrlProvider.java index e6c3e4911a..1ab798d60b 100644 --- a/scm-core/src/main/java/sonia/scm/url/UrlProvider.java +++ b/scm-core/src/main/java/sonia/scm/url/UrlProvider.java @@ -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(); diff --git a/scm-core/src/main/java/sonia/scm/url/WUIUrlProvider.java b/scm-core/src/main/java/sonia/scm/url/WUIUrlProvider.java index e6a8388e0d..bd94bf562e 100644 --- a/scm-core/src/main/java/sonia/scm/url/WUIUrlProvider.java +++ b/scm-core/src/main/java/sonia/scm/url/WUIUrlProvider.java @@ -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 * diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java index 7e4c410aa4..abe7fdd089 100644 --- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java @@ -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 diff --git a/scm-core/src/main/java/sonia/scm/web/filter/AutoLoginModules.java b/scm-core/src/main/java/sonia/scm/web/filter/AutoLoginModules.java new file mode 100644 index 0000000000..ae3010d4be --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/filter/AutoLoginModules.java @@ -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 + * + * @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; + } +} diff --git a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java index f88e25a961..c1c8931d47 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java @@ -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 */ diff --git a/scm-core/src/main/javadoc/overview.html b/scm-core/src/main/javadoc/overview.html index 208a72facb..c6deba5cf3 100644 --- a/scm-core/src/main/javadoc/overview.html +++ b/scm-core/src/main/javadoc/overview.html @@ -39,7 +39,7 @@

- + SCM-Manager

@@ -62,7 +62,7 @@

Architecture

- Architecture Overview + Architecture Overview
diff --git a/scm-core/src/main/resources/META-INF/scm/module.xml b/scm-core/src/main/resources/META-INF/scm/module.xml index cb275c30ee..85aba486d0 100644 --- a/scm-core/src/main/resources/META-INF/scm/module.xml +++ b/scm-core/src/main/resources/META-INF/scm/module.xml @@ -33,4 +33,7 @@ http://bitbucket.org/sdorra/scm-manager javax.servlet.ServletContextListener + + javax.servlet.http.HttpSessionListener + diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHookChangesetCollector.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHookChangesetCollector.java index 58b7ea0d75..7ba8b7106b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHookChangesetCollector.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHookChangesetCollector.java @@ -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 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 getExistingObjects(ReceivePack rpack) + private void collectChangesets(List changesets, + GitChangesetConverter converter, RevWalk walk, ReceiveCommand rc) + throws IncorrectObjectTypeException, IOException { - List existingObjects = Lists.newArrayList(); - - if (existingObjects == null) - { - Map 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 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 receiveCommands; + private final List receiveCommands; /** Field description */ - private ReceivePack rpack; + private final ReceivePack rpack; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index 114326834e..b2175f8989 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java @@ -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 -------------------------------------------------------------- /** diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractPushOrPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java similarity index 67% rename from scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractPushOrPullCommand.java rename to scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java index dfddfc1cc0..764dde47f3 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractPushOrPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java @@ -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 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; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java index ac2ebbfb77..ec0f468a57 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java @@ -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 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; + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java index d291860b6d..c64465194a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java @@ -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; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryViewer.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryViewer.java index bad6241ff8..b1915dc7ad 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryViewer.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryViewer.java @@ -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; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java index 83fb7e61e5..bf82193e84 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java @@ -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; } diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 37542b1403..be7b36716e 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -23,6 +23,12 @@ com.aragost.javahg javahg 0.8-SNAPSHOT + + + com.google.guava + guava + +
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgPushOrPullCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgPushOrPullCommand.java new file mode 100644 index 0000000000..43fb759433 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/AbstractHgPushOrPullCommand.java @@ -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 + */ +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; +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPullCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPullCommand.java index b8c34e9418..5243cbcc92 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPullCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPullCommand.java @@ -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 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; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java index 01ae9572fa..10b4af002e 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java @@ -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 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; } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBundleCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBundleCommand.java new file mode 100644 index 0000000000..af9dbe5fda --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBundleCommand.java @@ -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 + */ +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; + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java index 5c1907da7b..24180bfe9e 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java @@ -56,9 +56,12 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider { /** Field description */ - public static final Set COMMANDS = ImmutableSet.of(Command.BLAME, - Command.BROWSE, Command.CAT, - Command.DIFF, Command.LOG); + //J- + public static final Set 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 */ diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnUnbundleCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnUnbundleCommand.java new file mode 100644 index 0000000000..f8ebfc765a --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnUnbundleCommand.java @@ -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 + */ +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); + } + } +} diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBundleCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBundleCommandTest.java new file mode 100644 index 0000000000..57fde186cf --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBundleCommandTest.java @@ -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(); +} diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnUnbundleCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnUnbundleCommandTest.java new file mode 100644 index 0000000000..2c59797296 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnUnbundleCommandTest.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.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); + } +} diff --git a/scm-server/src/main/conf/server-config.xml b/scm-server/src/main/conf/server-config.xml index dd89ba65ae..561a6ad57f 100644 --- a/scm-server/src/main/conf/server-config.xml +++ b/scm-server/src/main/conf/server-config.xml @@ -127,5 +127,90 @@ +<<<<<<< mine +======= + + + + + + + + + + + + + + +>>>>>>> theirs diff --git a/scm-server/src/main/java/sonia/scm/server/ScmServer.java b/scm-server/src/main/java/sonia/scm/server/ScmServer.java index 07b0593386..440b904905 100644 --- a/scm-server/src/main/java/sonia/scm/server/ScmServer.java +++ b/scm-server/src/main/java/sonia/scm/server/ScmServer.java @@ -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 http://goo.gl/Zfy0Ev */ public void stopServer() { try { + server.setStopTimeout(GRACEFUL_TIMEOUT); server.setStopAtShutdown(true); + server.stop(); initialized = false; } catch (Exception ex) diff --git a/scm-server/src/main/java/sonia/scm/server/ScmServerDaemon.java b/scm-server/src/main/java/sonia/scm/server/ScmServerDaemon.java index 0799f7e69e..f7df769c30 100644 --- a/scm-server/src/main/java/sonia/scm/server/ScmServerDaemon.java +++ b/scm-server/src/main/java/sonia/scm/server/ScmServerDaemon.java @@ -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(); } diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 850adb37af..7ad0c9d1f6 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -490,10 +490,8 @@ default 2.28.0 1.28 - 1.13.1 1.0 - 3.0.5 - 0.8.15 + 0.8.17 Tomcat
diff --git a/scm-webapp/src/main/java/sonia/scm/HttpSessionListenerHolder.java b/scm-webapp/src/main/java/sonia/scm/HttpSessionListenerHolder.java new file mode 100644 index 0000000000..f7f5003989 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/HttpSessionListenerHolder.java @@ -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 + * @since 1.42 + */ +public class HttpSessionListenerHolder implements HttpSessionListener +{ + + /** key type of the session listeners */ + private static final Key> KEY = + Key.get(new TypeLiteral>() {} + ); + + /** 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 loadListeners( + HttpSessionEvent event) + { + Set 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 listenerSet; +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java index 619a8e9a2a..3a6e10a51f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java @@ -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.
*
*
    @@ -93,6 +96,6 @@ public class KeyResource //~--- fields --------------------------------------------------------------- - /** Field description */ + /** key generator */ private final KeyGenerator keyGenerator; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index b3a994112a..c6c78b0136 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -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.
    + * + * Status codes: + *
      + *
    • 201 created
    • + *
    • 400 bad request, the import bundle feature is not supported by this + * type of repositories or the parameters are not valid.
    • + *
    • 500 internal server error
    • + *
    • 409 conflict, a repository with the name already exists.
    • + *
    + * + * @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.
    + * + * Status codes: + *
      + *
    • 201 created
    • + *
    • 400 bad request, the import bundle feature is not supported by this + * type of repositories or the parameters are not valid.
    • + *
    • 500 internal server error
    • + *
    • 409 conflict, a repository with the name already exists.
    • + *
    * * - * @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.
    + * + * Status codes: + *
      + *
    • 201 created
    • + *
    • 400 bad request, the import by url feature is not supported by this + * type of repositories or the parameters are not valid.
    • + *
    • 409 conflict, a repository with the name already exists.
    • + *
    • 500 internal server error
    • + *
    + * + * @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.
    + *
    + * Status codes: + *
      + *
    • 200 ok, successful
    • + *
    • 400 bad request, the import feature is not + * supported by this type of repositories.
    • + *
    • 500 internal server error
    • + *
    + * + * @param type repository type + * + * @return imported repositories */ @POST @Path("{type}") @TypeHint(Repository[].class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) - public GenericEntity> importRepositories( - @PathParam("type") String type) + public Response importRepositories(@PathParam("type") String type) { - SecurityUtil.assertIsAdmin(); + SecurityUtils.getSubject().checkRole(Role.ADMIN); List repositories = new ArrayList(); + + importFromDirectory(repositories, type); + + //J- + return Response.ok( + new GenericEntity>(repositories) {} + ).build(); + //J+ + } + + /** + * Imports repositories of all supported types from the configured repository + * directories. This method requires admin privileges.
    + *
    + * Status codes: + *
      + *
    • 200 ok, successful
    • + *
    • 400 bad request, the import feature is not + * supported by this type of repositories.
    • + *
    • 500 internal server error
    • + *
    + * + * @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 repositories = new ArrayList(); + + for (Type t : findImportableTypes()) + { + importFromDirectory(repositories, t.getName()); + } + + //J- + return Response.ok( + new GenericEntity>(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.
    + *
    + * Status codes: + *
      + *
    • 200 ok, successful
    • + *
    • 400 bad request, the import feature is not + * supported by this type of repositories.
    • + *
    • 500 internal server error
    • + *
    + * + * @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 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.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>(repositories) {} - ; + return response; } //~--- get methods ---------------------------------------------------------- + /** + * Returns a list of repository types, which support the directory import + * feature. + * + * This method requires admin privileges.
    + *
    + * Status codes: + *
      + *
    • 200 ok, successful
    • + *
    • 400 bad request, the import feature is not + * supported by this type of repositories.
    • + *
    • 500 internal server error
    • + *
    + * + * @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 types = findImportableTypes(); + + //J- + return Response.ok( + new GenericEntity>(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 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> getImportableTypes() + private List findImportableTypes() { - SecurityUtil.assertIsAdmin(); - List types = new ArrayList(); Collection handlerTypes = manager.getTypes(); @@ -208,12 +678,217 @@ public class RepositoryImportResource } } - return new GenericEntity>(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 repositories, String type) + { + RepositoryHandler handler = manager.getHandler(type); + + if (handler != null) + { + logger.info("start directory import for repository type {}", type); + + try + { + List 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; } diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 9f336657a4..f1d6a67e90 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -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(); diff --git a/scm-webapp/src/main/webapp/WEB-INF/web.xml b/scm-webapp/src/main/webapp/WEB-INF/web.xml index ee29b55ece..97ea5d8b32 100644 --- a/scm-webapp/src/main/webapp/WEB-INF/web.xml +++ b/scm-webapp/src/main/webapp/WEB-INF/web.xml @@ -42,6 +42,10 @@ sonia.scm.boot.BootstrapContextListener + + + sonia.scm.HttpSessionListenerHolder + guiceFilter diff --git a/scm-webapp/src/main/webapp/index.mustache b/scm-webapp/src/main/webapp/index.mustache index e29c2cd556..4c2c3c913c 100644 --- a/scm-webapp/src/main/webapp/index.mustache +++ b/scm-webapp/src/main/webapp/index.mustache @@ -77,6 +77,7 @@ + diff --git a/scm-webapp/src/main/webapp/resources/css/style.css b/scm-webapp/src/main/webapp/resources/css/style.css index 2ce002295c..355e8afcbe 100644 --- a/scm-webapp/src/main/webapp/resources/css/style.css +++ b/scm-webapp/src/main/webapp/resources/css/style.css @@ -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; +} diff --git a/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js b/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js index 548599e3a0..61abb54b88 100644 --- a/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js +++ b/scm-webapp/src/main/webapp/resources/js/override/ext.form.field.js @@ -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; diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js index 53ea0dcf19..84e2b4009f 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.importwindow.js @@ -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. Note: 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([ + '

    ', + ' ', + ' Imported repositories
    ', + ' ', + ' - {.}
    ', + '
    ', + '
    ', + '
    ', + ' ', + ' Failed to import the following directories
    ', + ' ', + ' - {.}
    ', + '
    ', + '
    ', + ' ', + ' No repositories to import', + ' ', + '

    ', + '
    ' + ]), + + 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 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= 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'); } -}); \ No newline at end of file +}); + +// register xtype +Ext.reg('scmRepositoryImportWizard', Sonia.repository.ImportPanel); \ No newline at end of file diff --git a/scm-webapp/src/main/webapp/resources/js/util/sonia.util.tip.js b/scm-webapp/src/main/webapp/resources/js/util/sonia.util.tip.js new file mode 100644 index 0000000000..18cadb9884 --- /dev/null +++ b/scm-webapp/src/main/webapp/resources/js/util/sonia.util.tip.js @@ -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('\ +
    \ +
    \ +
    \ +
    \ +
    \ + \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ + {content}\ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    '), + + 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); \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java index fe4b7160e4..9931e52ad7 100644 --- a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java @@ -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 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); } }