This commit is contained in:
Mohamed Karray
2018-11-29 16:01:43 +01:00
62 changed files with 1253 additions and 983 deletions

View File

@@ -108,7 +108,7 @@ data:
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<configuration> <configuration>
<-- <!--
in a container environment we only need stdout in a container environment we only need stdout
--> -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

View File

@@ -29,6 +29,17 @@ spec:
volumeMounts: volumeMounts:
- name: data - name: data
mountPath: /data mountPath: /data
{{- if .Values.plugins }}
- name: install-plugins
image: alpine:3.8
imagePullPolicy: IfNotPresent
command: ['sh', '/scripts/install-plugins.sh']
volumeMounts:
- name: data
mountPath: /data
- name: scripts
mountPath: /scripts
{{- end }}
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
@@ -63,6 +74,11 @@ spec:
- name: config - name: config
configMap: configMap:
name: {{ include "scm-manager.fullname" . }} name: {{ include "scm-manager.fullname" . }}
{{- if .Values.plugins }}
- name: scripts
configMap:
name: {{ include "scm-manager.fullname" . }}-scripts
{{- end }}
{{- with .Values.nodeSelector }} {{- with .Values.nodeSelector }}
nodeSelector: nodeSelector:
{{ toYaml . | indent 8 }} {{ toYaml . | indent 8 }}

View File

@@ -0,0 +1,21 @@
{{- if .Values.plugins }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "scm-manager.fullname" . }}-scripts
labels:
app: {{ include "scm-manager.name" . }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
data:
install-plugins.sh: |
#!/bin/sh
mkdir -p /data/plugins
chown 1000:1000 /data/plugins
{{ range $i, $plugin := .Values.plugins }}
# install plugin {{ $plugin.name }}
wget -O /data/plugins/{{ $plugin.name }}.smp {{ $plugin.url }}
chown 1000:1000 /data/plugins/{{ $plugin.name }}.smp
{{ end }}
{{- end }}

View File

@@ -10,6 +10,10 @@ image:
tag: latest tag: latest
pullPolicy: Always pullPolicy: Always
# plugins:
# - name: scm-review-plugin
# url: https://oss.cloudogu.com/jenkins/job/scm-manager/job/scm-manager-bitbucket/job/scm-review-plugin/job/develop/lastSuccessfulBuild/artifact/target/scm-review-plugin-2.0.0-SNAPSHOT.smp
nameOverride: "" nameOverride: ""
fullnameOverride: "" fullnameOverride: ""

24
pom.xml
View File

@@ -142,6 +142,11 @@
<artifactId>junit-vintage-engine</artifactId> <artifactId>junit-vintage-engine</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId> <artifactId>hamcrest-core</artifactId>
@@ -159,6 +164,11 @@
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.assertj</groupId> <groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId> <artifactId>assertj-core</artifactId>
@@ -325,6 +335,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>0.3.0</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId> <artifactId>hamcrest-core</artifactId>
@@ -346,6 +363,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.assertj</groupId> <groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId> <artifactId>assertj-core</artifactId>

View File

@@ -35,6 +35,7 @@ package sonia.scm;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import sonia.scm.util.Util; import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -43,6 +44,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Path;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
@@ -105,8 +107,26 @@ public class BasicContextProvider implements SCMContextProvider
} }
} }
@VisibleForTesting
BasicContextProvider(File baseDirectory, String version, Stage stage) {
this.baseDirectory = baseDirectory;
this.version = version;
this.stage = stage;
}
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@Override
public Path resolve(Path path) {
if (path.isAbsolute()) {
return path;
}
return baseDirectory.toPath().resolve(path);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

View File

@@ -37,6 +37,7 @@ package sonia.scm;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.nio.file.Path;
/** /**
* The main class for retrieving the home and the version of the SCM-Manager. * The main class for retrieving the home and the version of the SCM-Manager.
@@ -65,6 +66,17 @@ public interface SCMContextProvider extends Closeable
*/ */
public File getBaseDirectory(); public File getBaseDirectory();
/**
* Resolves the given path against the base directory.
*
* @param path path to resolve
*
* @return absolute resolved path
*
* @since 2.0.0
*/
Path resolve(Path path);
/** /**
* Returns the current stage of SCM-Manager. * Returns the current stage of SCM-Manager.
* *

View File

@@ -172,7 +172,7 @@ public abstract class AbstractRepositoryHandler<C extends RepositoryConfig>
* @throws NotSupportedFeatureException * @throws NotSupportedFeatureException
*/ */
@Override @Override
public ImportHandler getImportHandler() throws NotSupportedFeatureException public ImportHandler getImportHandler()
{ {
throw new NotSupportedFeatureException("import"); throw new NotSupportedFeatureException("import");
} }

View File

@@ -76,7 +76,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
@Override @Override
public Repository create(Repository repository) { public Repository create(Repository repository) {
File nativeDirectory = resolveNativeDirectory(repository); File nativeDirectory = resolveNativeDirectory(repository.getId());
try { try {
create(repository, nativeDirectory); create(repository, nativeDirectory);
postCreate(repository, nativeDirectory); postCreate(repository, nativeDirectory);
@@ -106,10 +106,10 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
} }
@Override @Override
public File getDirectory(Repository repository) { public File getDirectory(String repositoryId) {
File directory; File directory;
if (isConfigured()) { if (isConfigured()) {
directory = resolveNativeDirectory(repository); directory = resolveNativeDirectory(repositoryId);
} else { } else {
throw new ConfigurationException("RepositoryHandler is not configured"); throw new ConfigurationException("RepositoryHandler is not configured");
} }
@@ -167,7 +167,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
return content; return content;
} }
private File resolveNativeDirectory(Repository repository) { private File resolveNativeDirectory(String repositoryId) {
return new File(repositoryLocationResolver.getRepositoryDirectory(repository), REPOSITORIES_NATIVE_DIRECTORY); return repositoryLocationResolver.getPath(repositoryId).resolve(REPOSITORIES_NATIVE_DIRECTORY).toFile();
} }
} }

View File

@@ -178,7 +178,7 @@ public abstract class DirectoryHealthCheck implements HealthCheck
else if (handler instanceof RepositoryDirectoryHandler) else if (handler instanceof RepositoryDirectoryHandler)
{ {
File directory = File directory =
((RepositoryDirectoryHandler) handler).getDirectory(repository); ((RepositoryDirectoryHandler) handler).getDirectory(repository.getId());
if (directory == null) if (directory == null)
{ {

View File

@@ -1,9 +1,7 @@
package sonia.scm.repository; package sonia.scm.repository;
import sonia.scm.SCMContextProvider; import java.nio.file.Path;
import java.nio.file.Paths;
import javax.inject.Inject;
import java.io.File;
/** /**
* A Location Resolver for File based Repository Storage. * A Location Resolver for File based Repository Storage.
@@ -19,35 +17,17 @@ import java.io.File;
*/ */
public class InitialRepositoryLocationResolver { public class InitialRepositoryLocationResolver {
public static final String DEFAULT_REPOSITORY_PATH = "repositories"; private static final String DEFAULT_REPOSITORY_PATH = "repositories";
private final SCMContextProvider context; /**
* Returns the initial path to repository.
@Inject *
public InitialRepositoryLocationResolver(SCMContextProvider context) { * @param repositoryId id of the repository
this.context = context; *
* @return initial path of repository
*/
public Path getPath(String repositoryId) {
return Paths.get(DEFAULT_REPOSITORY_PATH, repositoryId);
} }
public InitialRepositoryLocation getRelativeRepositoryPath(Repository repository) {
String relativePath = DEFAULT_REPOSITORY_PATH + File.separator + repository.getId();
return new InitialRepositoryLocation(new File(context.getBaseDirectory(), relativePath), relativePath);
}
public static class InitialRepositoryLocation {
private final File absolutePath;
private final String relativePath;
public InitialRepositoryLocation(File absolutePath, String relativePath) {
this.absolutePath = absolutePath;
this.relativePath = relativePath;
}
public File getAbsolutePath() {
return absolutePath;
}
public String getRelativePath() {
return relativePath;
}
}
} }

View File

@@ -11,7 +11,8 @@ import java.nio.file.Path;
public interface PathBasedRepositoryDAO extends RepositoryDAO { public interface PathBasedRepositoryDAO extends RepositoryDAO {
/** /**
* Get the current path of the repository. This works for existing repositories only, not for repositories that should be created. * Get the current path of the repository for the given id.
* This works for existing repositories only, not for repositories that should be created.
*/ */
Path getPath(Repository repository) ; Path getPath(String repositoryId) ;
} }

View File

@@ -43,9 +43,8 @@ import java.io.File;
public interface RepositoryDirectoryHandler extends RepositoryHandler { public interface RepositoryDirectoryHandler extends RepositoryHandler {
/** /**
* Get the current directory of the given repository * Get the current directory of the repository for the given id.
* @param repository
* @return the current directory of the given repository * @return the current directory of the given repository
*/ */
File getDirectory(Repository repository); File getDirectory(String repositoryId);
} }

View File

@@ -1,9 +1,10 @@
package sonia.scm.repository; package sonia.scm.repository;
import groovy.lang.Singleton; import groovy.lang.Singleton;
import sonia.scm.SCMContextProvider;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.nio.file.Path;
/** /**
* A Location Resolver for File based Repository Storage. * A Location Resolver for File based Repository Storage.
@@ -20,22 +21,33 @@ import java.io.File;
@Singleton @Singleton
public class RepositoryLocationResolver { public class RepositoryLocationResolver {
private RepositoryDAO repositoryDAO; private final SCMContextProvider contextProvider;
private InitialRepositoryLocationResolver initialRepositoryLocationResolver; private final RepositoryDAO repositoryDAO;
private final InitialRepositoryLocationResolver initialRepositoryLocationResolver;
@Inject @Inject
public RepositoryLocationResolver(RepositoryDAO repositoryDAO, InitialRepositoryLocationResolver initialRepositoryLocationResolver) { public RepositoryLocationResolver(SCMContextProvider contextProvider, RepositoryDAO repositoryDAO, InitialRepositoryLocationResolver initialRepositoryLocationResolver) {
this.contextProvider = contextProvider;
this.repositoryDAO = repositoryDAO; this.repositoryDAO = repositoryDAO;
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver; this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
} }
public File getRepositoryDirectory(Repository repository){ /**
* Returns the path to the repository.
*
* @param repositoryId repository id
*
* @return path of repository
*/
public Path getPath(String repositoryId) {
Path path;
if (repositoryDAO instanceof PathBasedRepositoryDAO) { if (repositoryDAO instanceof PathBasedRepositoryDAO) {
PathBasedRepositoryDAO pathBasedRepositoryDAO = (PathBasedRepositoryDAO) repositoryDAO; path = ((PathBasedRepositoryDAO) repositoryDAO).getPath(repositoryId);
return pathBasedRepositoryDAO.getPath(repository).toFile(); } else {
path = initialRepositoryLocationResolver.getPath(repositoryId);
} }
return initialRepositoryLocationResolver.getRelativeRepositoryPath(repository).getAbsolutePath();
return contextProvider.resolve(path);
} }
} }

View File

@@ -45,7 +45,7 @@ import javax.xml.stream.XMLStreamWriter;
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 1.31 * @since 1.31
*/ */
public final class IndentXMLStreamWriter implements XMLStreamWriter public final class IndentXMLStreamWriter implements XMLStreamWriter, AutoCloseable
{ {
/** line separator */ /** line separator */
@@ -475,7 +475,7 @@ public final class IndentXMLStreamWriter implements XMLStreamWriter
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** indent string */ /** indent string */
private String indent = " "; private String indent = " ";
/** current level */ /** current level */
private int level = 0; private int level = 0;

View File

@@ -0,0 +1,44 @@
package sonia.scm;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(TempDirectory.class)
class BasicContextProviderTest {
private Path baseDirectory;
private BasicContextProvider context;
@BeforeEach
void setUpContext(@TempDirectory.TempDir Path baseDirectory) {
this.baseDirectory = baseDirectory;
context = new BasicContextProvider(baseDirectory.toFile(), "x.y.z", Stage.PRODUCTION);
}
@Test
void shouldReturnAbsolutePathAsIs(@TempDirectory.TempDir Path path) {
Path absolutePath = path.toAbsolutePath();
Path resolved = context.resolve(absolutePath);
assertThat(resolved).isSameAs(absolutePath);
}
@Test
void shouldResolveRelatePath() {
Path path = Paths.get("repos", "42");
Path resolved = context.resolve(path);
assertThat(resolved).isAbsolute();
assertThat(resolved).startsWithRaw(baseDirectory);
assertThat(resolved).endsWithRaw(path);
}
}

View File

@@ -1,42 +1,23 @@
package sonia.scm.repository; package sonia.scm.repository;
import org.junit.Before; import org.junit.jupiter.api.Test;
import org.junit.Rule; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.Test; import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.SCMContextProvider;
import java.io.File; import java.io.File;
import java.io.IOException; import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class) @ExtendWith({MockitoExtension.class})
public class InitialRepositoryLocationResolverTest { class InitialRepositoryLocationResolverTest {
@Mock
private SCMContextProvider context;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Before
public void init() throws IOException {
when(context.getBaseDirectory()).thenReturn(temporaryFolder.newFolder());
}
@Test @Test
public void shouldComputeInitialDirectory() { void shouldComputeInitialPath() {
InitialRepositoryLocationResolver resolver = new InitialRepositoryLocationResolver(context); InitialRepositoryLocationResolver resolver = new InitialRepositoryLocationResolver();
Repository repository = new Repository(); Path path = resolver.getPath("42");
repository.setId("ABC");
InitialRepositoryLocationResolver.InitialRepositoryLocation directory = resolver.getRelativeRepositoryPath(repository);
assertThat(directory.getAbsolutePath()).isEqualTo(new File(context.getBaseDirectory(), "repositories/ABC")); assertThat(path).isRelative();
assertThat(directory.getRelativePath()).isEqualTo( "repositories/ABC"); assertThat(path.toString()).isEqualTo("repositories" + File.separator + "42");
} }
} }

View File

@@ -0,0 +1,65 @@
package sonia.scm.repository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
import sonia.scm.SCMContextProvider;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith({MockitoExtension.class})
class RepositoryLocationResolverTest {
@Mock
private SCMContextProvider contextProvider;
@Mock
private PathBasedRepositoryDAO pathBasedRepositoryDAO;
@Mock
private RepositoryDAO repositoryDAO;
@Mock
private InitialRepositoryLocationResolver initialRepositoryLocationResolver;
@BeforeEach
void beforeEach() {
when(contextProvider.resolve(any(Path.class))).then((Answer<Path>) invocationOnMock -> invocationOnMock.getArgument(0));
}
private RepositoryLocationResolver createResolver(RepositoryDAO pathBasedRepositoryDAO) {
return new RepositoryLocationResolver(contextProvider, pathBasedRepositoryDAO, initialRepositoryLocationResolver);
}
@Test
void shouldReturnPathFromDao() {
Path repositoryPath = Paths.get("repos", "42");
when(pathBasedRepositoryDAO.getPath("42")).thenReturn(repositoryPath);
RepositoryLocationResolver resolver = createResolver(pathBasedRepositoryDAO);
Path path = resolver.getPath("42");
assertThat(path).isSameAs(repositoryPath);
}
@Test
void shouldReturnInitialPathIfDaoIsNotPathBased() {
Path repositoryPath = Paths.get("r", "42");
when(initialRepositoryLocationResolver.getPath("42")).thenReturn(repositoryPath);
RepositoryLocationResolver resolver = createResolver(repositoryDAO);
Path path = resolver.getPath("42");
assertThat(path).isSameAs(repositoryPath);
}
}

View File

@@ -89,7 +89,7 @@ public class IndentXMLStreamWriterTest
StringBuilder buffer = new StringBuilder("<?xml version=\"1.0\" ?>"); StringBuilder buffer = new StringBuilder("<?xml version=\"1.0\" ?>");
buffer.append(IndentXMLStreamWriter.LINE_SEPARATOR); buffer.append(IndentXMLStreamWriter.LINE_SEPARATOR);
buffer.append("<root>").append(IndentXMLStreamWriter.LINE_SEPARATOR); buffer.append("<root>").append(IndentXMLStreamWriter.LINE_SEPARATOR);
buffer.append(" <message>Hello</message>"); buffer.append(" <message>Hello</message>");
buffer.append(IndentXMLStreamWriter.LINE_SEPARATOR); buffer.append(IndentXMLStreamWriter.LINE_SEPARATOR);
buffer.append("</root>").append(IndentXMLStreamWriter.LINE_SEPARATOR); buffer.append("</root>").append(IndentXMLStreamWriter.LINE_SEPARATOR);

View File

@@ -0,0 +1,50 @@
package sonia.scm.repository.xml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.nio.file.Path;
class MetadataStore {
private static final Logger LOG = LoggerFactory.getLogger(MetadataStore.class);
private final JAXBContext jaxbContext;
MetadataStore() {
try {
jaxbContext = JAXBContext.newInstance(Repository.class);
} catch (JAXBException ex) {
throw new IllegalStateException("failed to create jaxb context for repository", ex);
}
}
Repository read(Path path) {
LOG.trace("read repository metadata from {}", path);
try {
return (Repository) jaxbContext.createUnmarshaller().unmarshal(path.toFile());
} catch (JAXBException ex) {
throw new InternalRepositoryException(
ContextEntry.ContextBuilder.entity(Path.class, path.toString()).build(), "failed read repository metadata", ex
);
}
}
void write(Path path, Repository repository) {
LOG.trace("write repository metadata of {} to {}", repository.getNamespaceAndName(), path);
try {
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(repository, path.toFile());
} catch (JAXBException ex) {
throw new InternalRepositoryException(repository, "failed write repository metadata", ex);
}
}
}

View File

@@ -0,0 +1,145 @@
package sonia.scm.repository.xml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.xml.IndentXMLStreamWriter;
import sonia.scm.xml.XmlStreams;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
class PathDatabase {
private static final Logger LOG = LoggerFactory.getLogger(PathDatabase.class);
private static final String ENCODING = "UTF-8";
private static final String VERSION = "1.0";
private static final String ELEMENT_REPOSITORIES = "repositories";
private static final String ATTRIBUTE_CREATION_TIME = "creation-time";
private static final String ATTRIBUTE_LAST_MODIFIED = "last-modified";
private static final String ELEMENT_REPOSITORY = "repository";
private static final String ATTRIBUTE_ID = "id";
private final Path storePath;
PathDatabase(Path storePath){
this.storePath = storePath;
}
void write(Long creationTime, Long lastModified, Map<String, Path> pathDatabase) {
ensureParentDirectoryExists();
LOG.trace("write repository path database to {}", storePath);
try (IndentXMLStreamWriter writer = XmlStreams.createWriter(storePath)) {
writer.writeStartDocument(ENCODING, VERSION);
writeRepositoriesStart(writer, creationTime, lastModified);
for (Map.Entry<String, Path> e : pathDatabase.entrySet()) {
writeRepository(writer, e.getKey(), e.getValue());
}
writer.writeEndElement();
writer.writeEndDocument();
} catch (XMLStreamException | IOException ex) {
throw new InternalRepositoryException(
ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(),
"failed to write repository path database",
ex
);
}
}
private void ensureParentDirectoryExists() {
Path parent = storePath.getParent();
if (!Files.exists(parent)) {
try {
Files.createDirectories(parent);
} catch (IOException ex) {
throw new InternalRepositoryException(
ContextEntry.ContextBuilder.entity(Path.class, parent.toString()).build(),
"failed to create parent directory",
ex
);
}
}
}
private void writeRepositoriesStart(XMLStreamWriter writer, Long creationTime, Long lastModified) throws XMLStreamException {
writer.writeStartElement(ELEMENT_REPOSITORIES);
writer.writeAttribute(ATTRIBUTE_CREATION_TIME, String.valueOf(creationTime));
writer.writeAttribute(ATTRIBUTE_LAST_MODIFIED, String.valueOf(lastModified));
}
private void writeRepository(XMLStreamWriter writer, String id, Path value) throws XMLStreamException {
writer.writeStartElement(ELEMENT_REPOSITORY);
writer.writeAttribute(ATTRIBUTE_ID, id);
writer.writeCharacters(value.toString());
writer.writeEndElement();
}
void read(OnRepositories onRepositories, OnRepository onRepository) {
LOG.trace("read repository path database from {}", storePath);
XMLStreamReader reader = null;
try {
reader = XmlStreams.createReader(storePath);
while (reader.hasNext()) {
int eventType = reader.next();
if (eventType == XMLStreamReader.START_ELEMENT) {
String element = reader.getLocalName();
if (ELEMENT_REPOSITORIES.equals(element)) {
readRepositories(reader, onRepositories);
} else if (ELEMENT_REPOSITORY.equals(element)) {
readRepository(reader, onRepository);
}
}
}
} catch (XMLStreamException | IOException ex) {
throw new InternalRepositoryException(
ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(),
"failed to read repository path database",
ex
);
} finally {
XmlStreams.close(reader);
}
}
private void readRepository(XMLStreamReader reader, OnRepository onRepository) throws XMLStreamException {
String id = reader.getAttributeValue(null, ATTRIBUTE_ID);
Path path = Paths.get(reader.getElementText());
onRepository.handle(id, path);
}
private void readRepositories(XMLStreamReader reader, OnRepositories onRepositories) {
String creationTime = reader.getAttributeValue(null, ATTRIBUTE_CREATION_TIME);
String lastModified = reader.getAttributeValue(null, ATTRIBUTE_LAST_MODIFIED);
onRepositories.handle(Long.parseLong(creationTime), Long.parseLong(lastModified));
}
@FunctionalInterface
interface OnRepositories {
void handle(Long creationTime, Long lastModified);
}
@FunctionalInterface
interface OnRepository {
void handle(String id, Path path);
}
}

View File

@@ -1,101 +0,0 @@
package sonia.scm.repository.xml;
import org.apache.commons.lang.StringUtils;
import sonia.scm.ModelObject;
import sonia.scm.repository.Repository;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
@XmlRootElement(name = "repository-link")
@XmlAccessorType(XmlAccessType.FIELD)
public class RepositoryPath implements ModelObject {
private String path;
private String id;
private Long lastModified;
private Long creationDate;
@XmlTransient
private Repository repository;
@XmlTransient
private boolean toBeSynchronized;
/**
* Needed from JAXB
*/
public RepositoryPath() {
}
public RepositoryPath(String path, String id, Repository repository) {
this.path = path;
this.id = id;
this.repository = repository;
}
public Repository getRepository() {
return repository;
}
public void setRepository(Repository repository) {
this.repository = repository;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
@Override
public String getId() {
return id;
}
@Override
public void setLastModified(Long lastModified) {
this.lastModified = lastModified;
}
@Override
public Long getCreationDate() {
return creationDate;
}
@Override
public void setCreationDate(Long creationDate) {
this.creationDate = creationDate;
}
public void setId(String id) {
this.id = id;
}
@Override
public Long getLastModified() {
return lastModified;
}
@Override
public String getType() {
return getRepository()!= null? getRepository().getType():"";
}
@Override
public boolean isValid() {
return StringUtils.isNotEmpty(path);
}
public boolean toBeSynchronized() {
return toBeSynchronized;
}
public void setToBeSynchronized(boolean toBeSynchronized) {
this.toBeSynchronized = toBeSynchronized;
}
}

View File

@@ -1,19 +1,19 @@
/** /**
* Copyright (c) 2010, Sebastian Sdorra * Copyright (c) 2010, Sebastian Sdorra
* All rights reserved. * All rights reserved.
* * <p>
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
* * <p>
* 1. Redistributions of source code must retain the above copyright notice, * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, * 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation * this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution. * and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its * 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this * contributors may be used to endorse or promote products derived from this
* software without specific prior written permission. * software without specific prior written permission.
* * <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,9 +24,8 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 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 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* * <p>
* http://bitbucket.org/sdorra/scm-manager * http://bitbucket.org/sdorra/scm-manager
*
*/ */
@@ -34,153 +33,229 @@ package sonia.scm.repository.xml;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.io.FileSystem; import sonia.scm.io.FileSystem;
import sonia.scm.repository.InitialRepositoryLocationResolver; import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.InitialRepositoryLocationResolver.InitialRepositoryLocation;
import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.PathBasedRepositoryDAO; import sonia.scm.repository.PathBasedRepositoryDAO;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.store.JAXBConfigurationStore;
import sonia.scm.store.Store;
import sonia.scm.store.StoreConstants; import sonia.scm.store.StoreConstants;
import sonia.scm.xml.AbstractXmlDAO;
import java.io.File; import javax.inject.Inject;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Clock;
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.LinkedHashMap;
import java.util.Map;
/** /**
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Singleton @Singleton
public class XmlRepositoryDAO public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
extends AbstractXmlDAO<Repository, XmlRepositoryDatabase>
implements PathBasedRepositoryDAO {
public static final String STORE_NAME = "repositories"; private static final String STORE_NAME = "repositories";
private final PathDatabase pathDatabase;
private final MetadataStore metadataStore = new MetadataStore();
private InitialRepositoryLocationResolver initialRepositoryLocationResolver;
private final FileSystem fileSystem;
private final SCMContextProvider context; private final SCMContextProvider context;
private final InitialRepositoryLocationResolver locationResolver;
private final FileSystem fileSystem;
//~--- constructors --------------------------------------------------------- @VisibleForTesting
Clock clock = Clock.systemUTC();
private Long creationTime;
private Long lastModified;
private Map<String, Path> pathById;
private Map<String, Repository> byId;
private Map<NamespaceAndName, Repository> byNamespaceAndName;
@Inject @Inject
public XmlRepositoryDAO(InitialRepositoryLocationResolver initialRepositoryLocationResolver, FileSystem fileSystem, SCMContextProvider context) { public XmlRepositoryDAO(SCMContextProvider context, InitialRepositoryLocationResolver locationResolver, FileSystem fileSystem) {
super(new JAXBConfigurationStore<>(XmlRepositoryDatabase.class,
new File(context.getBaseDirectory(), Store.CONFIG.getGlobalStoreDirectory()+File.separator+ STORE_NAME + StoreConstants.FILE_EXTENSION)));
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
this.fileSystem = fileSystem;
this.context = context; this.context = context;
this.locationResolver = locationResolver;
this.fileSystem = fileSystem;
this.creationTime = clock.millis();
this.pathById = new LinkedHashMap<>();
this.byId = new LinkedHashMap<>();
this.byNamespaceAndName = new LinkedHashMap<>();
pathDatabase = new PathDatabase(createStorePath());
read();
} }
//~--- methods -------------------------------------------------------------- private void read() {
Path storePath = createStorePath();
@Override if (!Files.exists(storePath)) {
public boolean contains(NamespaceAndName namespaceAndName) { return;
return db.contains(namespaceAndName); }
pathDatabase.read(this::loadDates, this::loadRepository);
} }
//~--- get methods ---------------------------------------------------------- private void loadDates(Long creationTime, Long lastModified) {
this.creationTime = creationTime;
@Override this.lastModified = lastModified;
public Repository get(NamespaceAndName namespaceAndName) {
return db.get(namespaceAndName);
} }
//~--- methods -------------------------------------------------------------- private void loadRepository(String id, Path repositoryPath) {
Path metadataPath = createMetadataPath(context.resolve(repositoryPath));
Repository repository = metadataStore.read(metadataPath);
byId.put(id, repository);
byNamespaceAndName.put(repository.getNamespaceAndName(), repository);
pathById.put(id, repositoryPath);
}
@VisibleForTesting
Path createStorePath() {
return context.getBaseDirectory()
.toPath()
.resolve(StoreConstants.CONFIG_DIRECTORY_NAME)
.resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION));
}
@VisibleForTesting
Path createMetadataPath(Path repositoryPath) {
return repositoryPath.resolve(StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION));
}
@Override @Override
public void modify(Repository repository) { public String getType() {
RepositoryPath repositoryPath = findExistingRepositoryPath(repository).orElseThrow(() -> new InternalRepositoryException(repository, "path object for repository not found")); return "xml";
repositoryPath.setRepository(repository); }
repositoryPath.setToBeSynchronized(true);
storeDB(); @Override
public Long getCreationTime() {
return creationTime;
}
@Override
public Long getLastModified() {
return lastModified;
} }
@Override @Override
public void add(Repository repository) { public void add(Repository repository) {
InitialRepositoryLocation initialLocation = initialRepositoryLocationResolver.getRelativeRepositoryPath(repository); Repository clone = repository.clone();
Path repositoryPath = locationResolver.getPath(repository.getId());
Path resolvedPath = context.resolve(repositoryPath);
try { try {
fileSystem.create(initialLocation.getAbsolutePath()); fileSystem.create(resolvedPath.toFile());
Path metadataPath = createMetadataPath(resolvedPath);
metadataStore.write(metadataPath, repository);
synchronized (this) {
pathById.put(repository.getId(), repositoryPath);
byId.put(repository.getId(), clone);
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
writePathDatabase();
}
} catch (IOException e) { } catch (IOException e) {
throw new InternalRepositoryException(repository, "could not create directory for repository data: " + initialLocation.getAbsolutePath(), e); throw new InternalRepositoryException(repository, "failed to create filesystem", e);
}
RepositoryPath repositoryPath = new RepositoryPath(initialLocation.getRelativePath(), repository.getId(), repository.clone());
repositoryPath.setToBeSynchronized(true);
synchronized (store) {
db.add(repositoryPath);
storeDB();
} }
} }
private void writePathDatabase() {
lastModified = clock.millis();
pathDatabase.write(creationTime, lastModified, pathById);
}
@Override
public boolean contains(Repository repository) {
return byId.containsKey(repository.getId());
}
@Override
public boolean contains(NamespaceAndName namespaceAndName) {
return byNamespaceAndName.containsKey(namespaceAndName);
}
@Override
public boolean contains(String id) {
return byId.containsKey(id);
}
@Override
public Repository get(NamespaceAndName namespaceAndName) {
return byNamespaceAndName.get(namespaceAndName);
}
@Override @Override
public Repository get(String id) { public Repository get(String id) {
RepositoryPath repositoryPath = db.get(id); return byId.get(id);
if (repositoryPath != null) {
return repositoryPath.getRepository();
}
return null;
} }
@Override @Override
public Collection<Repository> getAll() { public Collection<Repository> getAll() {
return db.getRepositories(); return ImmutableList.copyOf(byNamespaceAndName.values());
} }
/**
* Method description
*
* @param repository
* @return
*/
@Override @Override
protected Repository clone(Repository repository) { public void modify(Repository repository) {
return repository.clone(); Repository clone = repository.clone();
synchronized (this) {
// remove old namespaceAndName from map, in case of rename
Repository prev = byId.put(clone.getId(), clone);
if (prev != null) {
byNamespaceAndName.remove(prev.getNamespaceAndName());
}
byNamespaceAndName.put(clone.getNamespaceAndName(), clone);
writePathDatabase();
}
Path repositoryPath = context.resolve(getPath(repository.getId()));
Path metadataPath = createMetadataPath(repositoryPath);
metadataStore.write(metadataPath, clone);
} }
@Override @Override
public void delete(Repository repository) { public void delete(Repository repository) {
Path directory = getPath(repository); Path path;
super.delete(repository); synchronized (this) {
Repository prev = byId.remove(repository.getId());
if (prev != null) {
byNamespaceAndName.remove(prev.getNamespaceAndName());
}
path = pathById.remove(repository.getId());
writePathDatabase();
}
path = context.resolve(path);
try { try {
fileSystem.destroy(directory.toFile()); fileSystem.destroy(path.toFile());
} catch (IOException e) { } catch (IOException e) {
throw new InternalRepositoryException(repository, "could not delete repository directory", e); throw new InternalRepositoryException(repository, "failed to destroy filesystem", e);
} }
} }
/**
* Method description
*
* @return
*/
@Override @Override
protected XmlRepositoryDatabase createNewDatabase() { public Path getPath(String repositoryId) {
return new XmlRepositoryDatabase(); return pathById.get(repositoryId);
}
@Override
public Path getPath(Repository repository) {
return context
.getBaseDirectory()
.toPath()
.resolve(
findExistingRepositoryPath(repository)
.map(RepositoryPath::getPath)
.orElseThrow(() -> new InternalRepositoryException(repository, "could not find base directory for repository")));
}
private Optional<RepositoryPath> findExistingRepositoryPath(Repository repository) {
return db.values().stream()
.filter(repoPath -> repoPath.getId().equals(repository.getId()))
.findAny();
} }
} }

View File

@@ -1,188 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository.xml;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.xml.XmlDatabase;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
//~--- JDK imports ------------------------------------------------------------
@XmlRootElement(name = "repository-db")
@XmlAccessorType(XmlAccessType.FIELD)
public class XmlRepositoryDatabase implements XmlDatabase<RepositoryPath> {
private Long creationTime;
private Long lastModified;
@XmlJavaTypeAdapter(XmlRepositoryMapAdapter.class)
@XmlElement(name = "repositories")
private Map<String, RepositoryPath> repositoryPathMap = new LinkedHashMap<>();
public XmlRepositoryDatabase() {
long c = System.currentTimeMillis();
creationTime = c;
lastModified = c;
}
static String createKey(NamespaceAndName namespaceAndName)
{
return namespaceAndName.getNamespace() + ":" + namespaceAndName.getName();
}
static String createKey(Repository repository)
{
return createKey(repository.getNamespaceAndName());
}
@Override
public void add(RepositoryPath repositoryPath)
{
repositoryPathMap.put(createKey(repositoryPath.getRepository()), repositoryPath);
}
public boolean contains(NamespaceAndName namespaceAndName)
{
return repositoryPathMap.containsKey(createKey(namespaceAndName));
}
@Override
public boolean contains(String id)
{
return get(id) != null;
}
@Override
public RepositoryPath remove(String id)
{
return repositoryPathMap.remove(createKey(get(id).getRepository()));
}
public Collection<Repository> getRepositories() {
List<Repository> repositories = new ArrayList<>();
for (RepositoryPath repositoryPath : repositoryPathMap.values()) {
Repository repository = repositoryPath.getRepository();
repositories.add(repository);
}
return repositories;
}
@Override
public Collection<RepositoryPath> values()
{
return repositoryPathMap.values();
}
public Repository get(NamespaceAndName namespaceAndName) {
RepositoryPath repositoryPath = repositoryPathMap.get(createKey(namespaceAndName));
if (repositoryPath != null) {
return repositoryPath.getRepository();
}
return null;
}
@Override
public RepositoryPath get(String id) {
return values().stream()
.filter(repoPath -> repoPath.getId().equals(id))
.findFirst()
.orElse(null);
}
/**
* Method description
*
*
* @return
*/
@Override
public long getCreationTime()
{
return creationTime;
}
/**
* Method description
*
*
* @return
*/
@Override
public long getLastModified()
{
return lastModified;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param creationTime
*/
@Override
public void setCreationTime(long creationTime)
{
this.creationTime = creationTime;
}
/**
* Method description
*
*
* @param lastModified
*/
@Override
public void setLastModified(long lastModified)
{
this.lastModified = lastModified;
}
}

View File

@@ -1,123 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository.xml;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.repository.Repository;
//~--- JDK imports ------------------------------------------------------------
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
*
* @author Sebastian Sdorra
*/
@XmlRootElement(name = "repositories")
@XmlAccessorType(XmlAccessType.FIELD)
public class XmlRepositoryList implements Iterable<RepositoryPath>
{
/**
* Constructs ...
*
*/
public XmlRepositoryList() {}
/**
* Constructs ...
*
*
*
* @param repositoryMap
*/
public XmlRepositoryList(Map<String, RepositoryPath> repositoryMap)
{
this.repositories = new LinkedList<>(repositoryMap.values());
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public Iterator<RepositoryPath> iterator()
{
return repositories.iterator();
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public LinkedList<RepositoryPath> getRepositoryPaths()
{
return repositories;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param repositories
*/
public void setRepositories(LinkedList<RepositoryPath> repositories)
{
this.repositories = repositories;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@XmlElement(name = "repository-path")
private LinkedList<RepositoryPath> repositories;
}

View File

@@ -1,112 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* <p>
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of 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.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 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.
* <p>
* http://bitbucket.org/sdorra/scm-manager
*/
package sonia.scm.repository.xml;
import sonia.scm.SCMContext;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import sonia.scm.store.StoreConstants;
import sonia.scm.store.StoreException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author Sebastian Sdorra
*/
public class XmlRepositoryMapAdapter extends XmlAdapter<XmlRepositoryList, Map<String, RepositoryPath>> {
@Override
public XmlRepositoryList marshal(Map<String, RepositoryPath> repositoryMap) {
XmlRepositoryList repositoryPaths = new XmlRepositoryList(repositoryMap);
try {
JAXBContext context = JAXBContext.newInstance(Repository.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
// marshall the repo_path/metadata.xml files
for (RepositoryPath repositoryPath : repositoryPaths.getRepositoryPaths()) {
if (repositoryPath.toBeSynchronized()) {
File baseDirectory = SCMContext.getContext().getBaseDirectory();
Path dir = baseDirectory.toPath().resolve(repositoryPath.getPath());
if (!Files.isDirectory(dir)) {
throw new InternalRepositoryException(repositoryPath.getRepository(), "repository path not found");
}
marshaller.marshal(repositoryPath.getRepository(), getRepositoryMetadataFile(dir.toFile()));
repositoryPath.setToBeSynchronized(false);
}
}
} catch (JAXBException ex) {
throw new StoreException("failed to marshal repository database", ex);
}
return repositoryPaths;
}
private File getRepositoryMetadataFile(File dir) {
return new File(dir, StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION));
}
@Override
public Map<String, RepositoryPath> unmarshal(XmlRepositoryList repositoryPaths) {
Map<String, RepositoryPath> repositoryPathMap = new LinkedHashMap<>();
try {
JAXBContext context = JAXBContext.newInstance(Repository.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
for (RepositoryPath repositoryPath : repositoryPaths) {
SCMContextProvider contextProvider = SCMContext.getContext();
File baseDirectory = contextProvider.getBaseDirectory();
Repository repository = (Repository) unmarshaller.unmarshal(getRepositoryMetadataFile(baseDirectory.toPath().resolve(repositoryPath.getPath()).toFile()));
repositoryPath.setRepository(repository);
repositoryPathMap.put(XmlRepositoryDatabase.createKey(repository), repositoryPath);
}
} catch (JAXBException ex) {
throw new StoreException("failed to unmarshal object", ex);
}
return repositoryPathMap;
}
}

View File

@@ -35,32 +35,14 @@ package sonia.scm.store;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Charsets;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
import sonia.scm.xml.IndentXMLStreamWriter; import sonia.scm.xml.IndentXMLStreamWriter;
import sonia.scm.xml.XmlStreams;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBElement;
@@ -68,11 +50,14 @@ import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller; import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter; import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
//~--- JDK imports ------------------------------------------------------------
/** /**
* *
@@ -255,74 +240,6 @@ public class JAXBConfigurationEntryStore<V> implements ConfigurationEntryStore<V
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param writer
*/
private void close(XMLStreamWriter writer)
{
if (writer != null)
{
try
{
writer.close();
}
catch (XMLStreamException ex)
{
logger.error("could not close writer", ex);
}
}
}
/**
* Method description
*
*
* @param reader
*/
private void close(XMLStreamReader reader)
{
if (reader != null)
{
try
{
reader.close();
}
catch (XMLStreamException ex)
{
logger.error("could not close reader", ex);
}
}
}
/**
* Method description
*
*
* @return
*
* @throws FileNotFoundException
*/
private Reader createReader() throws FileNotFoundException
{
return new InputStreamReader(new FileInputStream(file), Charsets.UTF_8);
}
/**
* Method description
*
*
* @return
*
* @throws FileNotFoundException
*/
private Writer createWriter() throws FileNotFoundException
{
return new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8);
}
/** /**
* Method description * Method description
* *
@@ -333,15 +250,13 @@ public class JAXBConfigurationEntryStore<V> implements ConfigurationEntryStore<V
{ {
logger.debug("load configuration from {}", file); logger.debug("load configuration from {}", file);
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
XMLStreamReader reader = null; XMLStreamReader reader = null;
try try
{ {
Unmarshaller u = context.createUnmarshaller(); Unmarshaller u = context.createUnmarshaller();
reader = xmlInputFactory.createXMLStreamReader(createReader()); reader = XmlStreams.createReader(file);
// configuration // configuration
reader.nextTag(); reader.nextTag();
@@ -390,7 +305,7 @@ public class JAXBConfigurationEntryStore<V> implements ConfigurationEntryStore<V
} }
finally finally
{ {
close(reader); XmlStreams.close(reader);
} }
} }
@@ -402,17 +317,8 @@ public class JAXBConfigurationEntryStore<V> implements ConfigurationEntryStore<V
{ {
logger.debug("store configuration to {}", file); logger.debug("store configuration to {}", file);
IndentXMLStreamWriter writer = null; try (IndentXMLStreamWriter writer = XmlStreams.createWriter(file))
try
{ {
//J-
writer = new IndentXMLStreamWriter(
XMLOutputFactory.newInstance().createXMLStreamWriter(
createWriter()
)
);
//J+
writer.writeStartDocument(); writer.writeStartDocument();
// configuration start // configuration start
@@ -453,10 +359,6 @@ public class JAXBConfigurationEntryStore<V> implements ConfigurationEntryStore<V
{ {
throw new StoreException("could not store configuration", ex); throw new StoreException("could not store configuration", ex);
} }
finally
{
close(writer);
}
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------

View File

@@ -0,0 +1,71 @@
package sonia.scm.xml;
import com.google.common.base.Charsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
public final class XmlStreams {
private static final Logger LOG = LoggerFactory.getLogger(XmlStreams.class);
private XmlStreams() {
}
public static void close(XMLStreamWriter writer) {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException ex) {
LOG.error("could not close writer", ex);
}
}
}
public static void close(XMLStreamReader reader) {
if (reader != null) {
try {
reader.close();
} catch (XMLStreamException ex) {
LOG.error("could not close reader", ex);
}
}
}
public static XMLStreamReader createReader(Path path) throws IOException, XMLStreamException {
return createReader(Files.newBufferedReader(path, Charsets.UTF_8));
}
public static XMLStreamReader createReader(File file) throws IOException, XMLStreamException {
return createReader(file.toPath());
}
private static XMLStreamReader createReader(Reader reader) throws XMLStreamException {
return XMLInputFactory.newInstance().createXMLStreamReader(reader);
}
public static IndentXMLStreamWriter createWriter(Path path) throws IOException, XMLStreamException {
return createWriter(Files.newBufferedWriter(path, Charsets.UTF_8));
}
public static IndentXMLStreamWriter createWriter(File file) throws IOException, XMLStreamException {
return createWriter(file.toPath());
}
private static IndentXMLStreamWriter createWriter(Writer writer) throws XMLStreamException {
return new IndentXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(writer));
}
}

View File

@@ -1,111 +1,362 @@
package sonia.scm.repository.xml; package sonia.scm.repository.xml;
import org.junit.Before;
import org.junit.Ignore; import com.google.common.base.Charsets;
import org.junit.Rule; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.rules.TemporaryFolder; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith; import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.io.DefaultFileSystem; import sonia.scm.io.DefaultFileSystem;
import sonia.scm.io.FileSystem; import sonia.scm.io.FileSystem;
import sonia.scm.repository.InitialRepositoryLocationResolver; import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.store.ConfigurationStore; import sonia.scm.repository.RepositoryTestData;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.StoreParameters;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Clock;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.codehaus.groovy.runtime.InvokerHelper.asList; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static sonia.scm.repository.xml.XmlRepositoryDAO.STORE_NAME;
@RunWith(MockitoJUnitRunner.class) @ExtendWith({MockitoExtension.class, TempDirectory.class})
@Ignore @MockitoSettings(strictness = Strictness.LENIENT)
public class XmlRepositoryDAOTest { class XmlRepositoryDAOTest {
@Mock
private ConfigurationStoreFactory storeFactory;
@Mock
private ConfigurationStore<XmlRepositoryDatabase> store;
@Mock
private XmlRepositoryDatabase db;
@Mock @Mock
private SCMContextProvider context; private SCMContextProvider context;
@Rule @Mock
public TemporaryFolder temporaryFolder = new TemporaryFolder(); private InitialRepositoryLocationResolver locationResolver;
private final FileSystem fileSystem = new DefaultFileSystem(); private FileSystem fileSystem = new DefaultFileSystem();
@Before private XmlRepositoryDAO dao;
public void init() throws IOException {
StoreParameters storeParameters = new StoreParameters().withType(XmlRepositoryDatabase.class).withName(STORE_NAME).build(); private Path baseDirectory;
when(storeFactory.getStore(storeParameters)).thenReturn(store);
when(store.get()).thenReturn(db); private AtomicLong atomicClock;
when(context.getBaseDirectory()).thenReturn(temporaryFolder.newFolder());
@BeforeEach
void createDAO(@TempDirectory.TempDir Path baseDirectory) {
this.baseDirectory = baseDirectory;
this.atomicClock = new AtomicLong();
when(locationResolver.getPath("42")).thenReturn(Paths.get("repos", "42"));
when(locationResolver.getPath("42+1")).thenReturn(Paths.get("repos", "puzzle"));
when(context.getBaseDirectory()).thenReturn(baseDirectory.toFile());
when(context.resolve(any(Path.class))).then(ic -> {
Path path = ic.getArgument(0);
return baseDirectory.resolve(path);
});
dao = createDAO();
}
private XmlRepositoryDAO createDAO() {
XmlRepositoryDAO dao = new XmlRepositoryDAO(context, locationResolver, fileSystem);
Clock clock = mock(Clock.class);
when(clock.millis()).then(ic -> atomicClock.incrementAndGet());
dao.clock = clock;
return dao;
} }
@Test @Test
public void addShouldCreateNewRepositoryPathWithRelativePath() { void shouldReturnXmlType() {
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(context); assertThat(dao.getType()).isEqualTo("xml");
XmlRepositoryDAO dao = new XmlRepositoryDAO(initialRepositoryLocationResolver, fileSystem, context);
dao.add(new Repository("id", "git", "namespace", "name"));
verify(db).add(argThat(repositoryPath -> {
assertThat(repositoryPath.getId()).isEqualTo("id");
assertThat(repositoryPath.getPath()).isEqualTo(InitialRepositoryLocationResolver.DEFAULT_REPOSITORY_PATH + "/id");
return true;
}));
verify(store).set(db);
} }
@Test @Test
public void modifyShouldStoreChangedRepository() { void shouldReturnCreationTimeAfterCreation() {
Repository oldRepository = new Repository("id", "old", null, null); long now = System.currentTimeMillis();
RepositoryPath repositoryPath = new RepositoryPath("/path", "id", oldRepository); assertThat(dao.getCreationTime()).isBetween(now - 200, now + 200);
when(db.values()).thenReturn(asList(repositoryPath));
XmlRepositoryDAO dao = new XmlRepositoryDAO(new InitialRepositoryLocationResolver(context), fileSystem, context);
Repository newRepository = new Repository("id", "new", null, null);
dao.modify(newRepository);
assertThat(repositoryPath.getRepository()).isSameAs(newRepository);
verify(store).set(db);
} }
@Test @Test
public void shouldGetPathInBaseDirectoryForRelativePath() { void shouldNotReturnLastModifiedAfterCreation() {
Repository existingRepository = new Repository("id", "old", null, null); assertThat(dao.getLastModified()).isNull();
RepositoryPath repositoryPath = new RepositoryPath("path", "id", existingRepository);
when(db.values()).thenReturn(asList(repositoryPath));
XmlRepositoryDAO dao = new XmlRepositoryDAO(new InitialRepositoryLocationResolver(context), fileSystem, context);
Path path = dao.getPath(existingRepository);
assertThat(path.toString()).isEqualTo(context.getBaseDirectory().getPath() + "/path");
} }
@Test @Test
public void shouldGetPathInBaseDirectoryForAbsolutePath() { void shouldReturnTrueForEachContainsMethod() {
Repository existingRepository = new Repository("id", "old", null, null); Repository heartOfGold = createHeartOfGold();
RepositoryPath repositoryPath = new RepositoryPath("/tmp/path", "id", existingRepository); dao.add(heartOfGold);
when(db.values()).thenReturn(asList(repositoryPath));
XmlRepositoryDAO dao = new XmlRepositoryDAO(new InitialRepositoryLocationResolver(context), fileSystem, context); assertThat(dao.contains(heartOfGold)).isTrue();
assertThat(dao.contains(heartOfGold.getId())).isTrue();
assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isTrue();
}
Path path = dao.getPath(existingRepository); private Repository createHeartOfGold() {
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
heartOfGold.setId("42");
return heartOfGold;
}
assertThat(path.toString()).isEqualTo("/tmp/path"); @Test
void shouldReturnFalseForEachContainsMethod() {
Repository heartOfGold = createHeartOfGold();
assertThat(dao.contains(heartOfGold)).isFalse();
assertThat(dao.contains(heartOfGold.getId())).isFalse();
assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isFalse();
}
@Test
void shouldReturnNullForEachGetMethod() {
assertThat(dao.get("42")).isNull();
assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isNull();
}
@Test
void shouldReturnRepository() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
assertThat(dao.get("42")).isEqualTo(heartOfGold);
assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isEqualTo(heartOfGold);
}
@Test
void shouldNotReturnTheSameInstance() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Repository repository = dao.get("42");
assertThat(repository).isNotSameAs(heartOfGold);
}
@Test
void shouldReturnAllRepositories() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Repository puzzle = createPuzzle();
dao.add(puzzle);
Collection<Repository> repositories = dao.getAll();
assertThat(repositories).containsExactlyInAnyOrder(heartOfGold, puzzle);
}
private Repository createPuzzle() {
Repository puzzle = RepositoryTestData.create42Puzzle();
puzzle.setId("42+1");
return puzzle;
}
@Test
void shouldModifyRepository() {
Repository heartOfGold = createHeartOfGold();
heartOfGold.setDescription("HeartOfGold");
dao.add(heartOfGold);
assertThat(dao.get("42").getDescription()).isEqualTo("HeartOfGold");
heartOfGold = createHeartOfGold();
heartOfGold.setDescription("Heart of Gold");
dao.modify(heartOfGold);
assertThat(dao.get("42").getDescription()).isEqualTo("Heart of Gold");
}
@Test
void shouldRemoveRepository() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
assertThat(dao.contains("42")).isTrue();
dao.delete(heartOfGold);
assertThat(dao.contains("42")).isFalse();
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
}
@Test
void shouldUpdateLastModifiedAfterEachWriteOperation() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Long firstLastModified = dao.getLastModified();
assertThat(firstLastModified).isNotNull();
Repository puzzle = createPuzzle();
dao.add(puzzle);
Long lastModifiedAdded = dao.getLastModified();
assertThat(lastModifiedAdded).isGreaterThan(firstLastModified);
heartOfGold.setDescription("Heart of Gold");
dao.modify(heartOfGold);
Long lastModifiedModified = dao.getLastModified();
assertThat(lastModifiedModified).isGreaterThan(lastModifiedAdded);
dao.delete(puzzle);
Long lastModifiedRemoved = dao.getLastModified();
assertThat(lastModifiedRemoved).isGreaterThan(lastModifiedModified);
}
@Test
void shouldRenameTheRepository() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
heartOfGold.setNamespace("hg2tg");
heartOfGold.setName("hog");
dao.modify(heartOfGold);
Repository repository = dao.get("42");
assertThat(repository.getNamespace()).isEqualTo("hg2tg");
assertThat(repository.getName()).isEqualTo("hog");
assertThat(dao.contains(new NamespaceAndName("hg2tg", "hog"))).isTrue();
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
}
@Test
void shouldDeleteRepositoryEvenWithChangedNamespace() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
heartOfGold.setNamespace("hg2tg");
heartOfGold.setName("hog");
dao.delete(heartOfGold);
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
}
@Test
void shouldReturnThePathForTheRepository() {
Path repositoryPath = Paths.get("r", "42");
when(locationResolver.getPath("42")).thenReturn(repositoryPath);
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Path path = dao.getPath("42");
assertThat(path).isEqualTo(repositoryPath);
}
@Test
void shouldCreateTheDirectoryForTheRepository() {
Path repositoryPath = Paths.get("r", "42");
when(locationResolver.getPath("42")).thenReturn(repositoryPath);
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Path path = getAbsolutePathFromDao("42");
assertThat(path).isDirectory();
}
@Test
void shouldRemoveRepositoryDirectoryAfterDeletion() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Path path = getAbsolutePathFromDao(heartOfGold.getId());
assertThat(path).isDirectory();
dao.delete(heartOfGold);
assertThat(path).doesNotExist();
}
private Path getAbsolutePathFromDao(String id) {
return context.resolve(dao.getPath(id));
}
@Test
void shouldCreateRepositoryPathDatabase() throws IOException {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Path storePath = dao.createStorePath();
assertThat(storePath).isRegularFile();
String content = content(storePath);
assertThat(content).contains(heartOfGold.getId());
assertThat(content).contains(dao.getPath(heartOfGold.getId()).toString());
}
private String content(Path storePath) throws IOException {
return new String(Files.readAllBytes(storePath), Charsets.UTF_8);
}
@Test
void shouldStoreRepositoryMetadataAfterAdd() throws IOException {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId());
Path metadataPath = dao.createMetadataPath(repositoryDirectory);
assertThat(metadataPath).isRegularFile();
String content = content(metadataPath);
assertThat(content).contains(heartOfGold.getName());
assertThat(content).contains(heartOfGold.getNamespace());
assertThat(content).contains(heartOfGold.getDescription());
}
@Test
void shouldUpdateRepositoryMetadataAfterModify() throws IOException {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
heartOfGold.setDescription("Awesome Spaceship");
dao.modify(heartOfGold);
Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId());
Path metadataPath = dao.createMetadataPath(repositoryDirectory);
String content = content(metadataPath);
assertThat(content).contains("Awesome Spaceship");
}
@Test
void shouldReadPathDatabaseAndMetadataOfRepositories() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
// reload data
dao = createDAO();
heartOfGold = dao.get("42");
assertThat(heartOfGold.getName()).isEqualTo("HeartOfGold");
Path path = getAbsolutePathFromDao(heartOfGold.getId());
assertThat(path).isDirectory();
}
@Test
void shouldReadCreationTimeAndLastModifedDateFromDatabase() {
Repository heartOfGold = createHeartOfGold();
dao.add(heartOfGold);
Long creationTime = dao.getCreationTime();
Long lastModified = dao.getLastModified();
// reload data
dao = createDAO();
assertThat(dao.getCreationTime()).isEqualTo(creationTime);
assertThat(dao.getLastModified()).isEqualTo(lastModified);
} }
} }

View File

@@ -121,7 +121,7 @@ public class GitGcTask implements Runnable {
} }
private void gc(Repository repository){ private void gc(Repository repository){
File file = repositoryHandler.getDirectory(repository); File file = repositoryHandler.getDirectory(repository.getId());
Git git = null; Git git = null;
try { try {
git = open(file); git = open(file);

View File

@@ -119,7 +119,7 @@ public abstract class AbstractGitIncomingOutgoingCommand
Git git = Git.wrap(open()); Git git = Git.wrap(open());
GitUtil.fetch(git, handler.getDirectory(remoteRepository), remoteRepository); GitUtil.fetch(git, handler.getDirectory(remoteRepository.getId()), remoteRepository);
ObjectId localId = getDefaultBranch(git.getRepository()); ObjectId localId = getDefaultBranch(git.getRepository());
ObjectId remoteId = null; ObjectId remoteId = null;

View File

@@ -196,7 +196,7 @@ public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand
*/ */
protected String getRemoteUrl(sonia.scm.repository.Repository repository) protected String getRemoteUrl(sonia.scm.repository.Repository repository)
{ {
return getRemoteUrl(handler.getDirectory(repository)); return getRemoteUrl(handler.getDirectory(repository.getId()));
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------

View File

@@ -196,12 +196,12 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand
private PullResponse pullFromScmRepository(Repository sourceRepository) private PullResponse pullFromScmRepository(Repository sourceRepository)
throws IOException throws IOException
{ {
File sourceDirectory = handler.getDirectory(sourceRepository); File sourceDirectory = handler.getDirectory(sourceRepository.getId());
Preconditions.checkArgument(sourceDirectory.exists(), Preconditions.checkArgument(sourceDirectory.exists(),
"source repository directory does not exists"); "source repository directory does not exists");
File targetDirectory = handler.getDirectory(repository); File targetDirectory = handler.getDirectory(repository.getId());
Preconditions.checkArgument(sourceDirectory.exists(), Preconditions.checkArgument(sourceDirectory.exists(),
"target repository directory does not exists"); "target repository directory does not exists");

View File

@@ -73,7 +73,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository) { public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository) {
this.handler = handler; this.handler = handler;
this.repository = repository; this.repository = repository;
this.context = new GitContext(handler.getDirectory(repository), repository); this.context = new GitContext(handler.getDirectory(repository.getId()), repository);
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------

View File

@@ -106,7 +106,7 @@ public class GitRepositoryResolver implements RepositoryResolver<HttpServletRequ
if (config.isValid()) if (config.isValid())
{ {
File gitdir = handler.getDirectory(repo); File gitdir = handler.getDirectory(repo.getId());
if (gitdir == null) { if (gitdir == null) {
throw new RepositoryNotFoundException(repositoryName); throw new RepositoryNotFoundException(repositoryName);
} }

View File

@@ -62,8 +62,6 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Mock @Mock
private GitWorkdirFactory gitWorkdirFactory; private GitWorkdirFactory gitWorkdirFactory;
RepositoryLocationResolver repositoryLocationResolver;
@Override @Override
protected void checkDirectory(File directory) { protected void checkDirectory(File directory) {
@@ -86,10 +84,10 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Override @Override
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
RepositoryLocationResolver locationResolver,
File directory) { File directory) {
repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider));
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
scheduler, repositoryLocationResolver, gitWorkdirFactory); scheduler, locationResolver, gitWorkdirFactory);
repositoryHandler.init(contextProvider); repositoryHandler.init(contextProvider);
GitConfig config = new GitConfig(); GitConfig config = new GitConfig();
@@ -103,7 +101,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Test @Test
public void getDirectory() { public void getDirectory() {
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
scheduler, repositoryLocationResolver, gitWorkdirFactory); scheduler, locationResolver, gitWorkdirFactory);
GitConfig config = new GitConfig(); GitConfig config = new GitConfig();
config.setDisabled(false); config.setDisabled(false);
config.setGcExpression("gc exp"); config.setGcExpression("gc exp");
@@ -111,7 +109,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
repositoryHandler.setConfig(config); repositoryHandler.setConfig(config);
initRepository(); initRepository();
File path = repositoryHandler.getDirectory(repository); File path = repositoryHandler.getDirectory(repository.getId());
assertEquals(repoPath.toString() + File.separator + AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); assertEquals(repoPath.toString() + File.separator + AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath());
} }
} }

View File

@@ -92,9 +92,9 @@ public class AbstractRemoteCommandTestBase
outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call(); outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call();
handler = mock(GitRepositoryHandler.class); handler = mock(GitRepositoryHandler.class);
when(handler.getDirectory(incomingRepository)).thenReturn( when(handler.getDirectory(incomingRepository.getId())).thenReturn(
incomingDirectory); incomingDirectory);
when(handler.getDirectory(outgoingRepository)).thenReturn( when(handler.getDirectory(outgoingRepository.getId())).thenReturn(
outgoingDirectory); outgoingDirectory);
} }

View File

@@ -124,7 +124,7 @@ public class AbstractHgHandler
protected AbstractHgHandler(HgRepositoryHandler handler, HgContext context, protected AbstractHgHandler(HgRepositoryHandler handler, HgContext context,
Repository repository) Repository repository)
{ {
this(handler, context, repository, handler.getDirectory(repository)); this(handler, context, repository, handler.getDirectory(repository.getId()));
} }
/** /**

View File

@@ -78,7 +78,7 @@ public class AbstractHgPushOrPullCommand extends AbstractCommand
if (repo != null) if (repo != null)
{ {
url = url =
handler.getDirectory(request.getRemoteRepository()).getAbsolutePath(); handler.getDirectory(request.getRemoteRepository().getId()).getAbsolutePath();
} }
else if (request.getRemoteUrl() != null) else if (request.getRemoteUrl() != null)
{ {

View File

@@ -81,7 +81,7 @@ public class HgIncomingCommand extends AbstractCommand
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ChangesetPagingResult getIncomingChangesets(IncomingCommandRequest request) { public ChangesetPagingResult getIncomingChangesets(IncomingCommandRequest request) {
File remoteRepository = handler.getDirectory(request.getRemoteRepository()); File remoteRepository = handler.getDirectory(request.getRemoteRepository().getId());
com.aragost.javahg.Repository repository = open(); com.aragost.javahg.Repository repository = open();

View File

@@ -83,7 +83,7 @@ public class HgOutgoingCommand extends AbstractCommand
public ChangesetPagingResult getOutgoingChangesets( public ChangesetPagingResult getOutgoingChangesets(
OutgoingCommandRequest request) OutgoingCommandRequest request)
{ {
File remoteRepository = handler.getDirectory(request.getRemoteRepository()); File remoteRepository = handler.getDirectory(request.getRemoteRepository().getId());
com.aragost.javahg.Repository repository = open(); com.aragost.javahg.Repository repository = open();

View File

@@ -81,7 +81,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
{ {
this.repository = repository; this.repository = repository;
this.handler = handler; this.handler = handler;
this.repositoryDirectory = handler.getDirectory(repository); this.repositoryDirectory = handler.getDirectory(repository.getId());
this.context = new HgCommandContext(hookManager, handler, repository, this.context = new HgCommandContext(hookManager, handler, repository,
repositoryDirectory); repositoryDirectory);
} }

View File

@@ -80,6 +80,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
/** Field description */ /** Field description */
public static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH"; public static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
/** Field description */
public static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
/** Field description */ /** Field description */
public static final String ENV_SESSION_PREFIX = "SCM_"; public static final String ENV_SESSION_PREFIX = "SCM_";
@@ -250,8 +253,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
HttpServletResponse response, Repository repository) HttpServletResponse response, Repository repository)
throws IOException, ServletException throws IOException, ServletException
{ {
String name = repository.getName(); File directory = handler.getDirectory(repository.getId());
File directory = handler.getDirectory(repository);
CGIExecutor executor = cgiExecutorFactory.createExecutor(configuration, CGIExecutor executor = cgiExecutorFactory.createExecutor(configuration,
getServletContext(), request, response); getServletContext(), request, response);
@@ -261,6 +263,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
executor.setStatusCodeHandler(exceptionHandler); executor.setStatusCodeHandler(exceptionHandler);
executor.setContentLengthWorkaround(true); executor.setContentLengthWorkaround(true);
executor.getEnvironment().set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); executor.getEnvironment().set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
executor.getEnvironment().set(ENV_REPOSITORY_ID, repository.getId());
executor.getEnvironment().set(ENV_REPOSITORY_PATH, executor.getEnvironment().set(ENV_REPOSITORY_PATH,
directory.getAbsolutePath()); directory.getAbsolutePath());

View File

@@ -35,6 +35,7 @@ package sonia.scm.web;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.io.Closeables; import com.google.common.io.Closeables;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -44,12 +45,10 @@ import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.repository.HgContext; import sonia.scm.repository.HgContext;
import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgHookManager;
import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.RepositoryHookType;
import sonia.scm.repository.api.HgHookMessage; import sonia.scm.repository.api.HgHookMessage;
import sonia.scm.repository.api.HgHookMessage.Severity; import sonia.scm.repository.api.HgHookMessage.Severity;
@@ -88,7 +87,7 @@ public class HgHookCallbackServlet extends HttpServlet
public static final String HGHOOK_PRE_RECEIVE = "pretxnchangegroup"; public static final String HGHOOK_PRE_RECEIVE = "pretxnchangegroup";
/** Field description */ /** Field description */
public static final String PARAM_REPOSITORYPATH = "repositoryPath"; public static final String PARAM_REPOSITORYID = "repositoryId";
/** Field description */ /** Field description */
private static final String PARAM_CHALLENGE = "challenge"; private static final String PARAM_CHALLENGE = "challenge";
@@ -170,7 +169,7 @@ public class HgHookCallbackServlet extends HttpServlet
if (m.matches()) if (m.matches())
{ {
File repositoryPath = getRepositoryPath(request); String repositoryId = getRepositoryId(request);
String type = m.group(1); String type = m.group(1);
String challenge = request.getParameter(PARAM_CHALLENGE); String challenge = request.getParameter(PARAM_CHALLENGE);
@@ -187,7 +186,7 @@ public class HgHookCallbackServlet extends HttpServlet
authenticate(request, credentials); authenticate(request, credentials);
} }
hookCallback(response, repositoryPath, type, challenge, node); hookCallback(response, type, repositoryId, challenge, node);
} }
else if (logger.isDebugEnabled()) else if (logger.isDebugEnabled())
{ {
@@ -246,7 +245,7 @@ public class HgHookCallbackServlet extends HttpServlet
} }
} }
private void fireHook(HttpServletResponse response, File repositoryDirectory, String node, RepositoryHookType type) private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type)
throws IOException throws IOException
{ {
HgHookContextProvider context = null; HgHookContextProvider context = null;
@@ -258,10 +257,10 @@ public class HgHookCallbackServlet extends HttpServlet
contextProvider.get().setPending(true); contextProvider.get().setPending(true);
} }
File repositoryDirectory = handler.getDirectory(repositoryId);
context = new HgHookContextProvider(handler, repositoryDirectory, hookManager, context = new HgHookContextProvider(handler, repositoryDirectory, hookManager,
node, type); node, type);
String repositoryId = getRepositoryId(repositoryDirectory);
hookEventFacade.handle(repositoryId).fireHookEvent(type, context); hookEventFacade.handle(repositoryId).fireHookEvent(type, context);
printMessages(response, context); printMessages(response, context);
@@ -280,7 +279,7 @@ public class HgHookCallbackServlet extends HttpServlet
} }
} }
private void hookCallback(HttpServletResponse response, File repositoryDirectory, String typeName, String challenge, String node) throws IOException { private void hookCallback(HttpServletResponse response, String typeName, String repositoryId, String challenge, String node) throws IOException {
if (hookManager.isAcceptAble(challenge)) if (hookManager.isAcceptAble(challenge))
{ {
RepositoryHookType type = null; RepositoryHookType type = null;
@@ -296,7 +295,7 @@ public class HgHookCallbackServlet extends HttpServlet
if (type != null) if (type != null)
{ {
fireHook(response, repositoryDirectory, node, type); fireHook(response, repositoryId, node, type);
} }
else else
{ {
@@ -441,21 +440,11 @@ public class HgHookCallbackServlet extends HttpServlet
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
@SuppressWarnings("squid:S2083") // we do nothing with the path given, so this should be no issue private String getRepositoryId(HttpServletRequest request)
private String getRepositoryId(File repositoryPath)
{ {
return handler.getRepositoryId(repositoryPath); String id = request.getParameter(PARAM_REPOSITORYID);
} Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "repository id not found in request");
return id;
private File getRepositoryPath(HttpServletRequest request) {
String path = request.getParameter(PARAM_REPOSITORYPATH);
if (Util.isNotEmpty(path)) {
return new File(path);
}
else
{
throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("directory", path), "could not find hgrc in directory");
}
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------

View File

@@ -41,6 +41,7 @@ import os, urllib, urllib2
baseUrl = os.environ['SCM_URL'] baseUrl = os.environ['SCM_URL']
challenge = os.environ['SCM_CHALLENGE'] challenge = os.environ['SCM_CHALLENGE']
credentials = os.environ['SCM_CREDENTIALS'] credentials = os.environ['SCM_CREDENTIALS']
repositoryId = os.environ['SCM_REPOSITORY_ID']
def printMessages(ui, msgs): def printMessages(ui, msgs):
for line in msgs: for line in msgs:
@@ -53,7 +54,7 @@ def callHookUrl(ui, repo, hooktype, node):
try: try:
url = baseUrl + hooktype url = baseUrl + hooktype
ui.debug( "send scm-hook to " + url + " and " + node + "\n" ) ui.debug( "send scm-hook to " + url + " and " + node + "\n" )
data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root}) data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root, 'repositoryId': repositoryId})
# open url but ignore proxy settings # open url but ignore proxy settings
proxy_handler = urllib2.ProxyHandler({}) proxy_handler = urllib2.ProxyHandler({})
opener = urllib2.build_opener(proxy_handler) opener = urllib2.build_opener(proxy_handler)

View File

@@ -50,7 +50,7 @@ import static org.junit.Assert.assertTrue;
/** /**
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.Silent.class)
public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Mock @Mock
@@ -59,8 +59,6 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Mock @Mock
private com.google.inject.Provider<HgContext> provider; private com.google.inject.Provider<HgContext> provider;
private RepositoryLocationResolver repositoryLocationResolver;
@Override @Override
protected void checkDirectory(File directory) { protected void checkDirectory(File directory) {
File hgDirectory = new File(directory, ".hg"); File hgDirectory = new File(directory, ".hg");
@@ -70,11 +68,8 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
} }
@Override @Override
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) {
File directory) { HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver);
repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider));
HgRepositoryHandler handler = new HgRepositoryHandler(factory,
new HgContextProvider(), repositoryLocationResolver);
handler.init(contextProvider); handler.init(contextProvider);
HgTestUtil.checkForSkip(handler); HgTestUtil.checkForSkip(handler);
@@ -84,8 +79,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Test @Test
public void getDirectory() { public void getDirectory() {
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver);
provider, repositoryLocationResolver);
HgConfig hgConfig = new HgConfig(); HgConfig hgConfig = new HgConfig();
hgConfig.setHgBinary("hg"); hgConfig.setHgBinary("hg");
@@ -93,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
repositoryHandler.setConfig(hgConfig); repositoryHandler.setConfig(hgConfig);
initRepository(); initRepository();
File path = repositoryHandler.getDirectory(repository); File path = repositoryHandler.getDirectory(repository.getId());
assertEquals(repoPath.toString() + File.separator + AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); assertEquals(repoPath.toString() + File.separator + AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath());
} }
} }

View File

@@ -103,7 +103,7 @@ public final class HgTestUtil
PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class); PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(context)); RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver());
HgRepositoryHandler handler = HgRepositoryHandler handler =
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver); new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver);
Path repoDir = directory.toPath(); Path repoDir = directory.toPath();

View File

@@ -41,6 +41,7 @@ import sonia.scm.Stage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
/** /**
* *
@@ -136,6 +137,11 @@ public class TempSCMContextProvider implements SCMContextProvider
this.baseDirectory = baseDirectory; this.baseDirectory = baseDirectory;
} }
@Override
public Path resolve(Path path) {
return baseDirectory.toPath().resolve(path);
}
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */

View File

@@ -94,9 +94,9 @@ public abstract class IncomingOutgoingTestBase extends AbstractTestBase
outgoing = Repository.create(createConfig(temp), outgoingDirectory); outgoing = Repository.create(createConfig(temp), outgoingDirectory);
handler = mock(HgRepositoryHandler.class); handler = mock(HgRepositoryHandler.class);
when(handler.getDirectory(incomingRepository)).thenReturn( when(handler.getDirectory(incomingRepository.getId())).thenReturn(
incomingDirectory); incomingDirectory);
when(handler.getDirectory(outgoingRepository)).thenReturn( when(handler.getDirectory(outgoingRepository.getId())).thenReturn(
outgoingDirectory); outgoingDirectory);
when(handler.getConfig()).thenReturn(temp.getConfig()); when(handler.getConfig()).thenReturn(temp.getConfig());
when(handler.getHgContext()).thenReturn(new HgContext()); when(handler.getHgContext()).thenReturn(new HgContext());

View File

@@ -63,7 +63,7 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
Repository repository) Repository repository)
{ {
this.repository = repository; this.repository = repository;
this.context = new SvnContext(handler.getDirectory(repository)); this.context = new SvnContext(handler.getDirectory(repository.getId()));
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------

View File

@@ -292,7 +292,7 @@ public class SvnDAVConfig extends DAVConfig
if (repository != null) if (repository != null)
{ {
directory = handler.getDirectory(repository); directory = handler.getDirectory(repository.getId());
} }
return directory; return directory;

View File

@@ -67,15 +67,10 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Mock @Mock
private com.google.inject.Provider<RepositoryManager> repositoryManagerProvider; private com.google.inject.Provider<RepositoryManager> repositoryManagerProvider;
@Mock
private RepositoryDAO repositoryDAO;
private HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); private HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class));
private HookEventFacade facade = new HookEventFacade(repositoryManagerProvider, hookContextFactory); private HookEventFacade facade = new HookEventFacade(repositoryManagerProvider, hookContextFactory);
private RepositoryLocationResolver repositoryLocationResolver;
@Override @Override
protected void checkDirectory(File directory) { protected void checkDirectory(File directory) {
File format = new File(directory, "format"); File format = new File(directory, "format");
@@ -91,9 +86,9 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Override @Override
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
RepositoryLocationResolver locationResolver,
File directory) { File directory) {
repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider)); SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver);
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, repositoryLocationResolver);
handler.init(contextProvider); handler.init(contextProvider);
@@ -109,13 +104,13 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
public void getDirectory() { public void getDirectory() {
when(factory.getStore(any())).thenReturn(store); when(factory.getStore(any())).thenReturn(store);
SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory, SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory,
facade, repositoryLocationResolver); facade, locationResolver);
SvnConfig svnConfig = new SvnConfig(); SvnConfig svnConfig = new SvnConfig();
repositoryHandler.setConfig(svnConfig); repositoryHandler.setConfig(svnConfig);
initRepository(); initRepository();
File path = repositoryHandler.getDirectory(repository); File path = repositoryHandler.getDirectory(repository.getId());
assertEquals(repoPath.toString()+File.separator+ AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath()); assertEquals(repoPath.toString()+File.separator+ AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY, path.getAbsolutePath());
} }
} }

View File

@@ -45,6 +45,7 @@ public final class RepositoryTestData {
.type(type) .type(type)
.contact("douglas.adams@hitchhiker.com") .contact("douglas.adams@hitchhiker.com")
.name("42Puzzle") .name("42Puzzle")
.namespace("hitchhiker")
.description("The 42 Puzzle") .description("The 42 Puzzle")
.build(); .build();
} }
@@ -59,6 +60,7 @@ public final class RepositoryTestData {
.type(type) .type(type)
.contact("zaphod.beeblebrox@hitchhiker.com") .contact("zaphod.beeblebrox@hitchhiker.com")
.name("happyVerticalPeopleTransporter") .name("happyVerticalPeopleTransporter")
.namespace("hitchhiker")
.description("Happy Vertical People Transporter") .description("Happy Vertical People Transporter")
.build(); .build();
} }
@@ -72,6 +74,7 @@ public final class RepositoryTestData {
.type(type) .type(type)
.contact("zaphod.beeblebrox@hitchhiker.com") .contact("zaphod.beeblebrox@hitchhiker.com")
.name("HeartOfGold") .name("HeartOfGold")
.namespace("hitchhiker")
.description( .description(
"Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive") "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive")
.build(); .build();
@@ -87,6 +90,7 @@ public final class RepositoryTestData {
.type(type) .type(type)
.contact("douglas.adams@hitchhiker.com") .contact("douglas.adams@hitchhiker.com")
.name("RestaurantAtTheEndOfTheUniverse") .name("RestaurantAtTheEndOfTheUniverse")
.namespace("hitchhiker")
.description("The Restaurant at the End of the Universe") .description("The Restaurant at the End of the Universe")
.build(); .build();
} }

View File

@@ -44,6 +44,7 @@ import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -62,7 +63,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
protected abstract void checkDirectory(File directory); protected abstract void checkDirectory(File directory);
protected abstract RepositoryHandler createRepositoryHandler( protected abstract RepositoryHandler createRepositoryHandler(
ConfigurationStoreFactory factory, File directory) throws IOException, RepositoryPathNotFoundException; ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) throws IOException, RepositoryPathNotFoundException;
@Test @Test
public void testCreate() { public void testCreate() {
@@ -74,7 +75,15 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
InMemoryConfigurationStoreFactory storeFactory = new InMemoryConfigurationStoreFactory(); InMemoryConfigurationStoreFactory storeFactory = new InMemoryConfigurationStoreFactory();
baseDirectory = new File(contextProvider.getBaseDirectory(), "repositories"); baseDirectory = new File(contextProvider.getBaseDirectory(), "repositories");
IOUtil.mkdirs(baseDirectory); IOUtil.mkdirs(baseDirectory);
handler = createRepositoryHandler(storeFactory, baseDirectory);
locationResolver = mock(RepositoryLocationResolver.class);
when(locationResolver.getPath(anyString())).then(ic -> {
String id = ic.getArgument(0);
return baseDirectory.toPath().resolve(id);
});
handler = createRepositoryHandler(storeFactory, locationResolver, baseDirectory);
} }
@Override @Override
@@ -98,11 +107,12 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
repository = RepositoryTestData.createHeartOfGold(); repository = RepositoryTestData.createHeartOfGold();
File repoDirectory = new File(baseDirectory, repository.getId()); File repoDirectory = new File(baseDirectory, repository.getId());
repoPath = repoDirectory.toPath(); repoPath = repoDirectory.toPath();
when(repoDao.getPath(repository)).thenReturn(repoPath); when(repoDao.getPath(repository.getId())).thenReturn(repoPath);
return new File(repoDirectory, AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY); return new File(repoDirectory, AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
} }
protected File baseDirectory; protected File baseDirectory;
protected RepositoryLocationResolver locationResolver;
private RepositoryHandler handler; private RepositoryHandler handler;
} }

View File

@@ -55,6 +55,7 @@ import static org.mockito.Mockito.*;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -213,6 +214,10 @@ public final class MockUtil
SCMContextProvider provider = mock(SCMContextProvider.class); SCMContextProvider provider = mock(SCMContextProvider.class);
when(provider.getBaseDirectory()).thenReturn(directory); when(provider.getBaseDirectory()).thenReturn(directory);
when(provider.resolve(any(Path.class))).then(ic -> {
Path p = ic.getArgument(0);
return directory.toPath().resolve(p);
});
return provider; return provider;
} }

View File

@@ -5,6 +5,7 @@
"private": true, "private": true,
"main": "src/index.js", "main": "src/index.js",
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.0.0",
"@fortawesome/fontawesome-free": "^5.3.1", "@fortawesome/fontawesome-free": "^5.3.1",
"@scm-manager/ui-extensions": "^0.1.1", "@scm-manager/ui-extensions": "^0.1.1",
"bulma": "^0.7.1", "bulma": "^0.7.1",
@@ -31,17 +32,19 @@
"redux": "^4.0.0", "redux": "^4.0.0",
"redux-devtools-extension": "^2.13.5", "redux-devtools-extension": "^2.13.5",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0" "redux-thunk": "^2.3.0",
"whatwg-fetch": "^3.0.0"
}, },
"scripts": { "scripts": {
"polyfills": "concat node_modules/@babel/polyfill/dist/polyfill.min.js node_modules/whatwg-fetch/dist/fetch.umd.js -o target/scm-ui/polyfills.bundle.js",
"webfonts": "copyfiles -f node_modules/@fortawesome/fontawesome-free/webfonts/* target/scm-ui/styles/webfonts", "webfonts": "copyfiles -f node_modules/@fortawesome/fontawesome-free/webfonts/* target/scm-ui/styles/webfonts",
"build-css": "node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/scm-ui/styles", "build-css": "node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/scm-ui/styles",
"watch-css": "npm run build-css && node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/scm-ui/styles --watch --recursive", "watch-css": "npm run build-css && node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/scm-ui/styles --watch --recursive",
"start-js": "ui-bundler serve --target target/scm-ui --vendor vendor.bundle.js", "start-js": "ui-bundler serve --target target/scm-ui --vendor vendor.bundle.js",
"start": "npm-run-all -p webfonts watch-css start-js", "start": "npm-run-all -p webfonts watch-css polyfills start-js",
"build-js": "ui-bundler bundle --mode=production target/scm-ui/scm-ui.bundle.js", "build-js": "ui-bundler bundle --mode=production target/scm-ui/scm-ui.bundle.js",
"build-vendor": "ui-bundler vendor --mode=production target/scm-ui/vendor.bundle.js", "build-vendor": "ui-bundler vendor --mode=production target/scm-ui/vendor.bundle.js",
"build": "npm-run-all -s webfonts build-css build-vendor build-js", "build": "npm-run-all -s webfonts build-css polyfills build-vendor build-js",
"test": "ui-bundler test", "test": "ui-bundler test",
"test-ci": "ui-bundler test --ci", "test-ci": "ui-bundler test --ci",
"flow": "flow", "flow": "flow",
@@ -49,6 +52,7 @@
}, },
"devDependencies": { "devDependencies": {
"@scm-manager/ui-bundler": "^0.0.21", "@scm-manager/ui-bundler": "^0.0.21",
"concat": "^1.0.3",
"copyfiles": "^2.0.0", "copyfiles": "^2.0.0",
"enzyme": "^3.3.0", "enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1", "enzyme-adapter-react-16": "^1.1.1",

View File

@@ -34,6 +34,7 @@
<script> <script>
window.ctxPath = "{{ contextPath }}"; window.ctxPath = "{{ contextPath }}";
</script> </script>
<script src="{{ contextPath }}/polyfills.bundle.js"></script>
<script src="{{ contextPath }}/vendor.bundle.js"></script> <script src="{{ contextPath }}/vendor.bundle.js"></script>
<script src="{{ contextPath }}/scm-ui.bundle.js"></script> <script src="{{ contextPath }}/scm-ui.bundle.js"></script>

View File

@@ -2,7 +2,7 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom";
import type { Repository, Branch } from "@scm-manager/ui-types"; import type { Branch, Repository } from "@scm-manager/ui-types";
import FileTree from "../components/FileTree"; import FileTree from "../components/FileTree";
import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import { ErrorNotification, Loading } from "@scm-manager/ui-components";
import BranchSelector from "../../containers/BranchSelector"; import BranchSelector from "../../containers/BranchSelector";
@@ -109,9 +109,9 @@ class Sources extends React.Component<Props> {
} }
renderBranchSelector = () => { renderBranchSelector = () => {
const { repository, branches, revision } = this.props; const { branches, revision } = this.props;
if (repository._links.branches) { if (branches) {
return ( return (
<BranchSelector <BranchSelector
branches={branches} branches={branches}

View File

@@ -513,6 +513,13 @@
"@babel/helper-regex" "^7.0.0" "@babel/helper-regex" "^7.0.0"
regexpu-core "^4.1.3" regexpu-core "^4.1.3"
"@babel/polyfill@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.0.0.tgz#c8ff65c9ec3be6a1ba10113ebd40e8750fb90bff"
dependencies:
core-js "^2.5.7"
regenerator-runtime "^0.11.1"
"@babel/preset-env@^7.0.0": "@babel/preset-env@^7.0.0":
version "7.1.0" version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.1.0.tgz#e67ea5b0441cfeab1d6f41e9b5c79798800e8d11" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.1.0.tgz#e67ea5b0441cfeab1d6f41e9b5c79798800e8d11"
@@ -2005,6 +2012,12 @@ concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0:
readable-stream "^2.2.2" readable-stream "^2.2.2"
typedarray "^0.0.6" typedarray "^0.0.6"
concat@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/concat/-/concat-1.0.3.tgz#40f3353089d65467695cb1886b45edd637d8cca8"
dependencies:
commander "^2.9.0"
connect-history-api-fallback@^1: connect-history-api-fallback@^1:
version "1.5.0" version "1.5.0"
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a"
@@ -2065,7 +2078,7 @@ copyfiles@^2.0.0:
through2 "^2.0.1" through2 "^2.0.1"
yargs "^11.0.0" yargs "^11.0.0"
core-js@^2.4.0, core-js@^2.5.0: core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7:
version "2.5.7" version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
@@ -2929,11 +2942,10 @@ event-emitter@^0.3.5:
es5-ext "~0.10.14" es5-ext "~0.10.14"
event-stream@~3.3.0: event-stream@~3.3.0:
version "3.3.6" version "3.3.5"
resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.6.tgz#cac1230890e07e73ec9cacd038f60a5b66173eef" resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.5.tgz#e5dd8989543630d94c6cf4d657120341fa31636b"
dependencies: dependencies:
duplexer "^0.1.1" duplexer "^0.1.1"
flatmap-stream "^0.1.0"
from "^0.1.7" from "^0.1.7"
map-stream "0.0.7" map-stream "0.0.7"
pause-stream "^0.0.11" pause-stream "^0.0.11"
@@ -3251,10 +3263,6 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2" graceful-fs "^4.1.2"
write "^0.2.1" write "^0.2.1"
flatmap-stream@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/flatmap-stream/-/flatmap-stream-0.1.1.tgz#d34f39ef3b9aa5a2fc225016bd3adf28ac5ae6ea"
flow-bin@^0.79.1: flow-bin@^0.79.1:
version "0.79.1" version "0.79.1"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.79.1.tgz#01c9f427baa6556753fa878c192d42e1ecb764b6" resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.79.1.tgz#01c9f427baa6556753fa878c192d42e1ecb764b6"
@@ -7056,7 +7064,7 @@ regenerator-runtime@^0.10.5:
version "0.10.5" version "0.10.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
regenerator-runtime@^0.11.0: regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
version "0.11.1" version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
@@ -8530,6 +8538,10 @@ whatwg-fetch@^2.0.4:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
whatwg-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
whatwg-mimetype@^2.1.0: whatwg-mimetype@^2.1.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171"

View File

@@ -0,0 +1,39 @@
package sonia.scm.api.v2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
/**
* Adds the Cache-Control: no-cache header to every api call. But only if non caching headers are set to the response.
* The Cache-Control header should fix stale resources on ie.
*/
@Provider
public class CacheControlResponseFilter implements ContainerResponseFilter {
private static final Logger LOG = LoggerFactory.getLogger(CacheControlResponseFilter.class);
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
if (!isCacheable(responseContext)) {
LOG.trace("add no-cache header to response");
responseContext.getHeaders().add("Cache-Control", "no-cache");
}
}
private boolean isCacheable(ContainerResponseContext responseContext) {
return hasLastModifiedDate(responseContext) || hasEntityTag(responseContext);
}
private boolean hasEntityTag(ContainerResponseContext responseContext) {
return responseContext.getEntityTag() != null;
}
private boolean hasLastModifiedDate(ContainerResponseContext responseContext) {
return responseContext.getLastModified() != null;
}
}

View File

@@ -0,0 +1,61 @@
package sonia.scm.api.v2;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MultivaluedMap;
import java.util.Date;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class CacheControlResponseFilterTest {
@Mock
private ContainerRequestContext requestContext;
@Mock
private ContainerResponseContext responseContext;
@Mock
private MultivaluedMap<String, Object> headers;
private CacheControlResponseFilter filter = new CacheControlResponseFilter();
@Before
public void setUpMocks() {
when(responseContext.getHeaders()).thenReturn(headers);
}
@Test
public void filterShouldAddCacheControlHeader() {
filter.filter(requestContext, responseContext);
verify(headers).add("Cache-Control", "no-cache");
}
@Test
public void filterShouldNotSetHeaderIfLastModifiedIsNotNull() {
when(responseContext.getLastModified()).thenReturn(new Date());
filter.filter(requestContext, responseContext);
verify(headers, never()).add("Cache-Control", "no-cache");
}
@Test
public void filterShouldNotSetHeaderIfEtagIsNotNull() {
when(responseContext.getEntityTag()).thenReturn(new EntityTag("42"));
filter.filter(requestContext, responseContext);
verify(headers, never()).add("Cache-Control", "no-cache");
}
}

View File

@@ -65,7 +65,6 @@ import sonia.scm.security.KeyGenerator;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -434,10 +433,10 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled, KeyGenerator keyGenerator) { private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled, KeyGenerator keyGenerator) {
DefaultFileSystem fileSystem = new DefaultFileSystem(); DefaultFileSystem fileSystem = new DefaultFileSystem();
Set<RepositoryHandler> handlerSet = new HashSet<>(); Set<RepositoryHandler> handlerSet = new HashSet<>();
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(contextProvider); ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider);
XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(initialRepositoryLocationResolver, fileSystem, contextProvider); InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver();
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repositoryDAO, initialRepositoryLocationResolver); XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, initialRepositoryLocationResolver, fileSystem);
ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver); RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(contextProvider, repositoryDAO, initialRepositoryLocationResolver);
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver)); handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver));
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) { handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
@Override @Override