Convert stub-runner base to Java

This commit is contained in:
Dave Syer
2016-07-15 17:10:40 +01:00
parent 65eaba0b23
commit 3a85b60408
54 changed files with 2532 additions and 1955 deletions

View File

@@ -1,6 +1,7 @@
package com.example.loan
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.test.context.ContextConfiguration
@@ -14,6 +15,7 @@ import com.example.loan.model.LoanApplicationStatus
@ContextConfiguration(loader = SpringBootContextLoader, classes = Application)
@AutoConfigureStubRunner
@IntegrationTest("debug=true")
class LoanApplicationServiceSpec extends Specification {
@Autowired

View File

@@ -39,7 +39,6 @@ import org.springframework.messaging.Message
* @author Marcin Grzejszczak
*/
@Configuration
@Import(StubRunnerConfiguration)
@CompileStatic
class StubRunnerIntegrationConfiguration {

View File

@@ -100,9 +100,6 @@
<executions>
<execution>
<goals>
<goal>addSources</goal>
<goal>addTestSources</goal>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import org.apache.maven.repository.internal.MavenRepositorySystemUtils
import org.eclipse.aether.DefaultRepositorySystemSession
import org.eclipse.aether.RepositorySystem
import org.eclipse.aether.RepositorySystemSession
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
import org.eclipse.aether.impl.DefaultServiceLocator
import org.eclipse.aether.repository.LocalRepository
import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.repository.RepositoryPolicy
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
import org.eclipse.aether.spi.connector.transport.TransporterFactory
import org.eclipse.aether.transport.file.FileTransporterFactory
import org.eclipse.aether.transport.http.HttpTransporterFactory
class AetherFactories {
private static final String MAVEN_LOCAL_REPOSITORY_LOCATION = 'maven.repo.local'
static RepositorySystem newRepositorySystem() {
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator()
locator.addService(RepositoryConnectorFactory, BasicRepositoryConnectorFactory)
locator.addService(TransporterFactory, FileTransporterFactory)
locator.addService(TransporterFactory, HttpTransporterFactory)
return locator.getService(RepositorySystem)
}
static RepositorySystemSession newSession(RepositorySystem system, boolean workOffline) {
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession()
session.setOffline(workOffline)
if (!workOffline) {
session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS)
}
LocalRepository localRepo = new LocalRepository(localRepositoryDirectory())
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo))
return session
}
static List<RemoteRepository> newRepositories(List<String> repositories) {
return repositories.withIndex()
.findAll { String repo, int index -> repo }
.collect { String repo, int index ->
new RemoteRepository.Builder('remote' + index, 'default', repo).build()
}
}
static String localRepositoryDirectory() {
System.getProperty(MAVEN_LOCAL_REPOSITORY_LOCATION, "${System.getProperty("user.home")}/.m2/repository")
}
}

View File

@@ -1,155 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.eclipse.aether.RepositorySystem
import org.eclipse.aether.RepositorySystemSession
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.resolution.ArtifactRequest
import org.eclipse.aether.resolution.ArtifactResult
import org.eclipse.aether.resolution.VersionRangeRequest
import org.eclipse.aether.resolution.VersionRangeResult
import org.eclipse.aether.resolution.VersionRequest
import org.eclipse.aether.resolution.VersionResult
import static AetherFactories.newRepositories
import static AetherFactories.newRepositorySystem
import static AetherFactories.newSession
import static org.springframework.cloud.contract.stubrunner.util.ZipCategory.unzipTo
import static java.nio.file.Files.createTempDirectory
/**
* @author Mariusz Smykula
*/
@CompileStatic
@Slf4j
class AetherStubDownloader implements StubDownloader {
private static final String TEMP_DIR_PREFIX = 'contracts'
private static final String ARTIFACT_EXTENSION = 'jar'
private static final String LATEST_ARTIFACT_VERSION = '(,]'
private static final String LATEST_VERSION_IN_IVY = '+'
private final List<RemoteRepository> remoteRepos
private final RepositorySystem repositorySystem
private final RepositorySystemSession session
AetherStubDownloader(StubRunnerOptions stubRunnerOptions) {
this.remoteRepos = remoteRepositories(stubRunnerOptions)
if (!remoteRepos) {
log.error('Remote repositories for stubs are not specified!')
}
this.repositorySystem = newRepositorySystem()
this.session = newSession(this.repositorySystem, stubRunnerOptions.workOffline)
}
/**
* Used by the Maven Plugin
*
* @param repositorySystem
* @param remoteRepositories - remote artifact repositories
* @param session
* @param workOffline
*/
AetherStubDownloader(RepositorySystem repositorySystem,
List<RemoteRepository> remoteRepositories,
RepositorySystemSession session) {
this.remoteRepos = remoteRepositories
this.repositorySystem = repositorySystem
this.session = session
if (!remoteRepos) {
log.error('Remote remoteRepositories for stubs are not specified!')
}
}
private List<RemoteRepository> remoteRepositories(StubRunnerOptions stubRunnerOptions) {
return newRepositories(stubRunnerOptions.stubRepositoryRoot.split(',').toList())
}
private File unpackedJar(String resolvedVersion, String stubsGroup, String stubsModule, String classifier) {
log.info("Resolved version is [$resolvedVersion]")
if (!resolvedVersion) {
log.warn("Stub for group [$stubsGroup] module [$stubsModule] and classifier [$classifier] not found in $remoteRepos")
return null
}
Artifact artifact = new DefaultArtifact(stubsGroup, stubsModule, classifier, ARTIFACT_EXTENSION, resolvedVersion)
ArtifactRequest request = new ArtifactRequest(artifact: artifact, repositories: remoteRepos)
log.info("Resolving artifact '$artifact' using remote repositories $remoteRepos.url")
try {
ArtifactResult result = repositorySystem.resolveArtifact(session, request)
log.info("Resolved artifact $artifact to ${result.artifact.file}")
File temporaryFile = unpackStubJarToATemporaryFolder(result.artifact.file.toURI())
log.info("Unpacked file to [$temporaryFile]")
return temporaryFile
} catch (Exception e) {
log.warn("Exception occured while trying to download a stub for group [$stubsGroup] module [$stubsModule] and classifier [$classifier] in $remoteRepos", e)
return null
}
}
private String getVersion(String stubsGroup, String stubsModule, String version, String classifier) {
if (!version || LATEST_VERSION_IN_IVY == version) {
log.info("Desired version is [$version] - will try to resolve the latest version")
return resolveHighestArtifactVersion(stubsGroup, stubsModule, classifier)
}
log.info("Will try to resolve version [$version]")
return resolveArtifactVersion(stubsGroup, stubsModule, version, classifier)
}
@Override
Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(StubRunnerOptions options, StubConfiguration stubConfiguration) {
String version = getVersion(stubConfiguration.groupId, stubConfiguration.artifactId, stubConfiguration.version, stubConfiguration.classifier)
File unpackedJar = unpackedJar(version, stubConfiguration.groupId, stubConfiguration.artifactId,
stubConfiguration.classifier)
if (!unpackedJar) {
return null
}
return new AbstractMap.SimpleEntry(new StubConfiguration(stubConfiguration.groupId, stubConfiguration.artifactId, version, stubConfiguration.classifier),
unpackedJar)
}
private String resolveHighestArtifactVersion(String stubsGroup, String stubsModule, String classifier) {
Artifact artifact = new DefaultArtifact(stubsGroup, stubsModule, classifier, ARTIFACT_EXTENSION, LATEST_ARTIFACT_VERSION)
VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, remoteRepos, null)
VersionRangeResult rangeResult = repositorySystem.resolveVersionRange(session, versionRangeRequest)
if (!rangeResult.highestVersion) {
log.error("Version was not resolved!")
}
return rangeResult.highestVersion ?: ''
}
private String resolveArtifactVersion(String stubsGroup, String stubsModule, String version, String classifier) {
Artifact artifact = new DefaultArtifact(stubsGroup, stubsModule, classifier, ARTIFACT_EXTENSION, version)
VersionRequest versionRequest = new VersionRequest(artifact, remoteRepos, null)
VersionResult versionResult = repositorySystem.resolveVersion(session, versionRequest)
return versionResult.version ?: ''
}
private static File unpackStubJarToATemporaryFolder(URI stubJarUri) {
File tmpDirWhereStubsWillBeUnzipped = createTempDirectory(TEMP_DIR_PREFIX).toFile()
tmpDirWhereStubsWillBeUnzipped.deleteOnExit()
log.info("Unpacking stub from JAR [URI: ${stubJarUri}]")
unzipTo(new File(stubJarUri), tmpDirWhereStubsWillBeUnzipped)
return tmpDirWhereStubsWillBeUnzipped
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.transform.ToString
/**
* Arguments passed to the {@link StubRunner} application
*
* @see StubRunner
*/
@CompileStatic
@ToString(includeNames = true)
@PackageScope
class Arguments {
final StubRunnerOptions stubRunnerOptions
final String context
final String repositoryPath
final StubConfiguration stub
Arguments(StubRunnerOptions stubRunnerOptions, String repositoryPath = "", StubConfiguration stub = null) {
this.stubRunnerOptions = stubRunnerOptions
this.context = context
this.repositoryPath = repositoryPath
this.stub = stub
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
/**
* Tries to execute a closure with an available port from the given range
*/
@CompileStatic
@Slf4j
@PackageScope
class AvailablePortScanner {
private static final int MAX_RETRY_COUNT = 1000
private final int minPortNumber
private final int maxPortNumber
private final int maxRetryCount
AvailablePortScanner(int minPortNumber, int maxPortNumber, int maxRetryCount = MAX_RETRY_COUNT) {
checkPortRanges(minPortNumber, maxPortNumber)
this.minPortNumber = minPortNumber
this.maxPortNumber = maxPortNumber
this.maxRetryCount = maxRetryCount
}
private void checkPortRanges(int minPortNumber, int maxPortNumber) {
if (minPortNumber > maxPortNumber) {
throw new InvalidPortRange(minPortNumber, maxPortNumber)
}
}
public <T> T tryToExecuteWithFreePort(Closure<T> closure) {
for (i in (1..maxRetryCount)) {
try {
int numberOfPortsToBind = maxPortNumber - minPortNumber + 1
int portToScan = new Random().nextInt(numberOfPortsToBind) + minPortNumber
checkIfPortIsAvailable(portToScan)
return executeLogicForAvailablePort(portToScan, closure)
} catch (BindException exception) {
log.debug("Failed to execute closure (try: $i/$maxRetryCount)", exception)
}
}
throw new NoPortAvailableException(minPortNumber, maxPortNumber)
}
private <T> T executeLogicForAvailablePort(int portToScan, Closure<T> closure) {
log.debug("Trying to execute closure with port [$portToScan]")
return closure(portToScan)
}
private void checkIfPortIsAvailable(int portToScan) {
ServerSocket socket = null
try {
socket = new ServerSocket(portToScan)
} finally {
socket?.close()
}
}
static class NoPortAvailableException extends RuntimeException {
protected NoPortAvailableException(int lowerBound, int upperBound) {
super("Could not find available port in range $lowerBound:$upperBound")
}
}
static class InvalidPortRange extends RuntimeException {
protected InvalidPortRange(int lowerBound, int upperBound) {
super("Invalid bounds exceptions, min port [$lowerBound] is greater to max port [$upperBound]")
}
}
}

View File

@@ -1,142 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import org.springframework.cloud.contract.spec.Contract
/**
* Manages lifecycle of multiple {@link StubRunner} instances.
*
* @see StubRunner
*/
@CompileStatic
class BatchStubRunner implements StubRunning {
private final Iterable<StubRunner> stubRunners
BatchStubRunner(Iterable<StubRunner> stubRunners) {
this.stubRunners = stubRunners
}
@Override
RunningStubs runStubs() {
Map<StubConfiguration, Integer> appsAndPorts = stubRunners.inject([:]) { Map<StubConfiguration, Integer> acc, StubRunner value ->
RunningStubs runningStubs = value.runStubs()
acc.putAll(runningStubs.validNamesAndPorts())
return acc
} as Map<StubConfiguration, Integer>
return new RunningStubs(appsAndPorts)
}
@Override
URL findStubUrl(String groupId, String artifactId) {
return stubRunners.findResult(null) { StubRunner stubRunner ->
return stubRunner.findStubUrl(groupId, artifactId)
} as URL
}
@Override
URL findStubUrl(String ivyNotation) {
String[] splitString = ivyNotation.split(":")
if (splitString.length > 3) {
throw new IllegalArgumentException("$ivyNotation is invalid")
} else if (splitString.length == 2) {
return findStubUrl(splitString[0], splitString[1])
}
return findStubUrl(null, splitString[0])
}
@Override
RunningStubs findAllRunningStubs() {
return new RunningStubs(stubRunners.collect { StubRunner runner -> runner.findAllRunningStubs() })
}
@Override
Map<StubConfiguration, Collection<Contract>> getContracts() {
return stubRunners.inject([:]) { Map<StubConfiguration, Collection<Contract>> map, StubRunner stubRunner ->
map.putAll(stubRunner.contracts ?: [:])
return map
} as Map<StubConfiguration, Collection<Contract>>
}
@Override
boolean trigger(String ivyNotation, String labelName) {
boolean triggered = stubRunners.inject(false) { boolean acc, StubRunner stubRunner ->
boolean success = stubRunner.trigger(ivyNotation, labelName)
if (acc) {
return true
}
return success
}
if (!triggered) {
throw new IllegalArgumentException("No label with name [$labelName] for " +
"dependency [$ivyNotation] was found. Here you have the list of dependencies " +
"and their labels [${ivyToLabels()}]")
}
return triggered
}
private String ivyToLabels() {
return labels().entrySet().collect {
"Dependency [${it.key}] has labels ${it.value}]"
}.join('\n')
}
@Override
boolean trigger(String labelName) {
boolean triggered = stubRunners.inject(false) { boolean acc, StubRunner stubRunner ->
boolean success = stubRunner.trigger(labelName)
if (acc) {
return true
}
return success
}
if (!triggered) {
throw new IllegalArgumentException("No label with name [$labelName] was found. " +
"Here you have the list of dependencies and their labels [${ivyToLabels()}")
}
return triggered
}
@Override
boolean trigger() {
return stubRunners.inject(false) { boolean acc, StubRunner stubRunner ->
boolean success = stubRunner.trigger()
if (acc) {
return true
}
return success
}
}
@Override
Map<String, Collection<String>> labels() {
return stubRunners.inject([:]) { Map<String, Collection<String>> map, StubRunner stubRunner ->
if (stubRunner) {
map.putAll(stubRunner.labels() ?: [:])
}
return map
} as Map<String, Collection<String>>
}
@Override
void close() throws IOException {
stubRunners.each {
it.close()
}
}
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
/**
* Structure representing executed stubs. Contains the configuration of each stub
* together with the port on which its executed.
*/
@EqualsAndHashCode
@CompileStatic
class RunningStubs {
final Map<StubConfiguration, Integer> namesAndPorts = [:]
RunningStubs(Map<StubConfiguration, Integer> map) {
this.namesAndPorts.putAll(map)
}
RunningStubs(Collection<RunningStubs> runningStubs) {
runningStubs.each {
this.namesAndPorts.putAll(it.namesAndPorts)
}
}
Integer getPort(String artifactId) {
return getEntry(artifactId)?.value
}
Map.Entry<StubConfiguration, Integer> getEntry(String artifactId) {
return namesAndPorts.entrySet().find {
it.key.matchesIvyNotation(artifactId)
}
}
Integer getPort(String groupId, String artifactId) {
return namesAndPorts.entrySet().find {
it.key.matchesIvyNotation("$groupId:$artifactId")
}?.value
}
boolean isPresent(String artifactId) {
return getEntry(artifactId)
}
boolean isPresent(String groupId, String artifactId) {
return namesAndPorts.entrySet().find {
it.key.matchesIvyNotation("$groupId:$artifactId")
}
}
Set<StubConfiguration> getAllServices() {
return namesAndPorts.keySet()
}
Set<String> getAllServicesNames() {
return namesAndPorts.keySet().collect { it.artifactId } as Set
}
Map<String, Integer> toIvyToPortMapping() {
return namesAndPorts.collectEntries { [(it.key.toColonSeparatedDependencyNotation()) : it.value] } as Map<String, Integer>
}
Map<StubConfiguration, Integer> validNamesAndPorts() {
return namesAndPorts.findAll { StubConfiguration key, Integer value -> value != -1 }
}
@Override
String toString() {
return namesAndPorts.collect {
"Stub [${it.key.toColonSeparatedDependencyNotation()}] is running on port [${it.value}]"
}.join("\n")
}
}

View File

@@ -1,156 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import org.springframework.cloud.contract.stubrunner.util.StringUtils
/**
* Represents a configuration of a single stub. The stub can be described
* by groupId:artifactId:classifier notation
*/
@CompileStatic
@EqualsAndHashCode
public class StubConfiguration {
private static final String STUB_COLON_DELIMITER = ":"
private static final String DEFAULT_VERSION = "+"
private static final String DEFAULT_CLASSIFIER = "stubs"
final String groupId
final String artifactId
final String version
final String classifier
public StubConfiguration(String groupId, String artifactId, String version) {
this.groupId = groupId
this.artifactId = artifactId
this.version = version
this.classifier = DEFAULT_CLASSIFIER
}
public StubConfiguration(String groupId, String artifactId, String version, String classifier) {
this.groupId = groupId
this.artifactId = artifactId
this.version = version
this.classifier = classifier
}
public StubConfiguration(String stubPath, String defaultClassifier) {
String[] parsedPath = parsedPathEmptyByDefault(stubPath, STUB_COLON_DELIMITER, defaultClassifier)
this.groupId = parsedPath[0]
this.artifactId = parsedPath[1]
this.version = parsedPath[2]
this.classifier = parsedPath[3]
}
public StubConfiguration(String stubPath) {
String[] parsedPath = parsedPathEmptyByDefault(stubPath, STUB_COLON_DELIMITER, DEFAULT_CLASSIFIER)
this.groupId = parsedPath[0]
this.artifactId = parsedPath[1]
this.version = parsedPath[2]
this.classifier = parsedPath[3]
}
private List<String> parsedPathEmptyByDefault(String path, String delimiter, String defaultClassifier) {
String[] splitPath = path.split(delimiter)
String stubsGroupId = ""
String stubsArtifactId = ""
String stubsVersion = ""
String stubsClassifier = ""
if (splitPath.length >= 2) {
stubsGroupId = splitPath[0]
stubsArtifactId = splitPath[1]
stubsVersion = splitPath.length >= 3 ? splitPath[2] : DEFAULT_VERSION
stubsClassifier = splitPath.length == 4 ? splitPath[3] : defaultClassifier
}
return [stubsGroupId, stubsArtifactId, stubsVersion, stubsClassifier]
}
private boolean isDefined() {
return StringUtils.hasText(groupId) && StringUtils.hasText(this.artifactId)
}
String toColonSeparatedDependencyNotation() {
if(!isDefined()) {
return ""
}
return [groupId, artifactId, version, classifier].join(STUB_COLON_DELIMITER)
}
@CompileDynamic
boolean groupIdAndArtifactMatches(String ivyNotationAsString) {
def (String groupId, String artifactId) = ivyNotationFrom(ivyNotationAsString)
if (!groupId) {
return this.artifactId == artifactId
}
return this.groupId == groupId && this.artifactId == artifactId
}
boolean equals(o) {
if (this.is(o)) return true
if (getClass() != o.class) return false
StubConfiguration that = (StubConfiguration) o
if (artifactId != that.artifactId) return false
if (groupId != that.groupId) return false
return true
}
int hashCode() {
int result
result = (groupId != null ? groupId.hashCode() : 0)
result = 31 * result + (artifactId != null ? artifactId.hashCode() : 0)
return result
}
boolean matchesIvyNotation(String ivyNotationAsString) {
def strings = ivyNotationAsString.split(':')
if (strings.length == 1) {
return artifactId == ivyNotationAsString
} else if(strings.length == 2) {
return groupId == strings[0] &&
artifactId == strings[1]
} else if(strings.length == 3) {
return groupId == strings[0] &&
artifactId == strings[1] &&
(strings[2] == DEFAULT_VERSION || version == strings[2])
}
return groupId == strings[0] &&
artifactId == strings[1] &&
(strings[2] == DEFAULT_VERSION || version == strings[2]) &&
classifier == strings[3]
}
private String[] ivyNotationFrom(String ivyNotation) {
String[] splitString = ivyNotation.split(":")
if (splitString.length == 1) {
// assuming that ivy notation represents artifactId only
return [null, splitString[0]] as String[]
}
return [splitString[0], splitString[1]] as String[]
}
@Override
public String toString() {
return toColonSeparatedDependencyNotation()
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import org.springframework.cloud.contract.spec.Contract
/**
* @author Marcin Grzejszczak
*/
@CompileStatic
@EqualsAndHashCode
class StubData {
final Integer port
final List<Contract> contracts
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
/**
* Wraps the folder with WireMock mappings.
*/
@CompileStatic
@PackageScope
@Slf4j
class StubRepository {
private final File path
final List<WiremockMappingDescriptor> projectDescriptors
final Collection<Contract> contracts
StubRepository(File repository) {
if (!repository.isDirectory()) {
throw new FileNotFoundException("Missing descriptor repository under path [$path]")
}
this.path = repository
this.projectDescriptors = projectDescriptors()
this.contracts = contracts()
}
/**
* Returns a list of {@link Contract}
*/
private Collection<Contract> contracts() {
List<Contract> contracts = []
contracts.addAll(contractDescriptors())
return contracts
}
/**
* Returns the list of WireMock JSON files wrapped in {@link WiremockMappingDescriptor}
*/
private List<WiremockMappingDescriptor> projectDescriptors() {
List<WiremockMappingDescriptor> mappingDescriptors = []
mappingDescriptors.addAll(contextDescriptors())
return mappingDescriptors
}
private List<WiremockMappingDescriptor> contextDescriptors() {
return (path.exists() ? collectMappingDescriptors(path) : []) as List<WiremockMappingDescriptor>
}
private List<WiremockMappingDescriptor> collectMappingDescriptors(File descriptorsDirectory) {
List<WiremockMappingDescriptor> mappingDescriptors = []
descriptorsDirectory.eachFileRecurse { File file ->
if (isMappingDescriptor(file)) {
mappingDescriptors << new WiremockMappingDescriptor(file)
}
}
return mappingDescriptors
}
private Collection<Contract> contractDescriptors() {
return (path.exists() ? collectContractDescriptors(path) : []) as Collection<Contract>
}
private Collection<Contract> collectContractDescriptors(File descriptorsDirectory) {
List<Contract> mappingDescriptors = []
descriptorsDirectory.eachFileRecurse { File file ->
if (isContractDescriptor(file)) {
try {
mappingDescriptors << ContractVerifierDslConverter.convert(file)
} catch (Exception e) {
log.warn("Exception occurred while trying to parse file [$file]", e)
}
}
}
return mappingDescriptors
}
private static boolean isMappingDescriptor(File file) {
return file.isFile() && file.name.endsWith('.json')
}
private static boolean isContractDescriptor(File file) {
//TODO: Consider script injections implications...
return file.isFile() && file.name.endsWith('.groovy')
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging
import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierMessaging
/**
* Represents a single instance of ready-to-run stubs.
* Can run the stubs and then will return the name of the collaborator together with
* its URI.
* Can also be queried if the current groupid and artifactid are matching the
* corresponding running stub.
*/
@Slf4j
@CompileStatic
class StubRunner implements StubRunning {
private final StubRepository stubRepository
private final StubConfiguration stubsConfiguration
private final StubRunnerOptions stubRunnerOptions
private final StubRunnerExecutor localStubRunner
private final ContractVerifierMessaging contractVerifierMessaging
@Deprecated
StubRunner(Arguments arguments) {
this(arguments.stubRunnerOptions, arguments.repositoryPath, arguments.stub)
}
StubRunner(StubRunnerOptions stubRunnerOptions, String repositoryPath, StubConfiguration stubsConfiguration) {
this(stubRunnerOptions, repositoryPath, stubsConfiguration, new NoOpContractVerifierMessaging())
}
StubRunner(StubRunnerOptions stubRunnerOptions, String repositoryPath, StubConfiguration stubsConfiguration,
ContractVerifierMessaging contractVerifierMessaging) {
this.stubsConfiguration = stubsConfiguration
this.stubRunnerOptions = stubRunnerOptions
this.stubRepository = new StubRepository(new File(repositoryPath))
AvailablePortScanner portScanner = new AvailablePortScanner(stubRunnerOptions.minPortValue,
stubRunnerOptions.maxPortValue)
this.contractVerifierMessaging = contractVerifierMessaging
this.localStubRunner = new StubRunnerExecutor(portScanner, contractVerifierMessaging)
}
@Override
RunningStubs runStubs() {
registerShutdownHook()
return localStubRunner.runStubs(stubRunnerOptions,stubRepository, stubsConfiguration)
}
@Override
URL findStubUrl(String groupId, String artifactId) {
return localStubRunner.findStubUrl(groupId, artifactId)
}
@Override
URL findStubUrl(String ivyNotation) {
String[] splitString = ivyNotation.split(":")
if (splitString.length == 1) {
// assuming that ivy notation represents artifactId only
return findStubUrl(null, splitString[0])
}
return findStubUrl(splitString[0], splitString[1])
}
@Override
RunningStubs findAllRunningStubs() {
return localStubRunner.findAllRunningStubs()
}
@Override
Map<StubConfiguration, Collection<Contract>> getContracts() {
return localStubRunner.getContracts()
}
@Override
boolean trigger(String ivyNotation, String labelName) {
return localStubRunner.trigger(ivyNotation, labelName)
}
@Override
boolean trigger(String labelName) {
return localStubRunner.trigger(labelName)
}
@Override
boolean trigger() {
return localStubRunner.trigger()
}
@Override
Map<String, Collection<String>> labels() {
return localStubRunner.labels()
}
private void registerShutdownHook() {
Runnable stopAllServers = { this.close() }
Runtime.runtime.addShutdownHook(new Thread(stopAllServers))
}
@Override
void close() throws IOException {
localStubRunner?.shutdown()
}
}

View File

@@ -1,164 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.json.JsonOutput
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessage
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging
import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierMessaging
import org.springframework.cloud.contract.verifier.util.BodyExtractor
/**
* Runs stubs for a particular {@link StubServer}
*/
@CompileStatic
@Slf4j
class StubRunnerExecutor implements StubFinder {
private final AvailablePortScanner portScanner
private final ContractVerifierMessaging contractVerifierMessaging
private StubServer stubServer
StubRunnerExecutor(AvailablePortScanner portScanner, ContractVerifierMessaging contractVerifierMessaging) {
this.portScanner = portScanner
this.contractVerifierMessaging = contractVerifierMessaging
}
StubRunnerExecutor(AvailablePortScanner portScanner) {
this.portScanner = portScanner
this.contractVerifierMessaging = new NoOpContractVerifierMessaging()
}
RunningStubs runStubs(StubRunnerOptions stubRunnerOptions, StubRepository repository, StubConfiguration stubConfiguration) {
startStubServers(stubRunnerOptions, stubConfiguration, repository)
RunningStubs runningCollaborators =
new RunningStubs([(stubServer.stubConfiguration): stubServer.port])
log.info("All stubs are now running [${runningCollaborators.toString()}")
return runningCollaborators
}
void shutdown() {
stubServer?.stop()
}
@Override
URL findStubUrl(String groupId, String artifactId) {
if (!groupId) {
return returnStubUrlIfMatches(stubServer.stubConfiguration.artifactId == artifactId)
}
return returnStubUrlIfMatches(stubServer.stubConfiguration.artifactId == artifactId &&
stubServer.stubConfiguration.groupId == groupId)
}
@Override
URL findStubUrl(String ivyNotation) {
String[] splitString = ivyNotation.split(":")
if (splitString.length == 1) {
throw new IllegalArgumentException("$ivyNotation is invalid")
}
return findStubUrl(splitString[0], splitString[1])
}
@Override
RunningStubs findAllRunningStubs() {
return new RunningStubs([(stubServer.stubConfiguration) : stubServer.port])
}
@Override
Map<StubConfiguration, Collection<Contract>> getContracts() {
return [(stubServer.stubConfiguration): stubServer.contracts]
}
@Override
boolean trigger(String ivyNotationAsString, String labelName) {
Collection<Contract> matchingContracts = getContracts().findAll {
it.key.groupIdAndArtifactMatches(ivyNotationAsString)
}.values().flatten() as Collection<Contract>
return triggerForDsls(matchingContracts, labelName)
}
@Override
boolean trigger(String labelName) {
return triggerForDsls(getContracts().values().flatten() as Collection<Contract>, labelName)
}
private boolean triggerForDsls(Collection<Contract> dsls, String labelName) {
Collection<Contract> matchingDsls = dsls.findAll { it.label == labelName }
if (matchingDsls.empty) {
return false
}
matchingDsls.each {
sendMessageIfApplicable(it)
}
return true
}
@Override
boolean trigger() {
(getContracts().values().flatten() as Collection<Contract>).each { Contract groovyDsl ->
sendMessageIfApplicable(groovyDsl)
}
return true
}
@Override
Map<String, Collection<String>> labels() {
return getContracts().collectEntries {
[(it.key.toColonSeparatedDependencyNotation()) : it.value.collect { it.label }]
} as Map<String, Collection<String>>
}
private void sendMessageIfApplicable(Contract groovyDsl) {
if (!groovyDsl.outputMessage) {
return
}
ContractVerifierMessage message = contractVerifierMessaging.create(
new JsonOutput().toJson(BodyExtractor.extractClientValueFromBody(groovyDsl.outputMessage?.body?.clientValue)),
groovyDsl.outputMessage?.headers?.asStubSideMap())
contractVerifierMessaging.send(message, groovyDsl.outputMessage.sentTo.clientValue)
}
private URL returnStubUrlIfMatches(boolean condition) {
return condition ? stubServer.stubUrl : null
}
private void startStubServers(StubRunnerOptions stubRunnerOptions, StubConfiguration stubConfiguration, StubRepository repository) {
List<WiremockMappingDescriptor> mappings = repository.getProjectDescriptors()
Collection<Contract> contracts = repository.contracts
Integer port = stubRunnerOptions.port(stubConfiguration)
if (port) {
stubServer = new StubServer(port, stubConfiguration, mappings, contracts)
} else {
stubServer = portScanner.tryToExecuteWithFreePort { int availablePort ->
return new StubServer(availablePort, stubConfiguration, mappings, contracts)
}
}
if (!contracts.empty && !contracts.any { it.request }) {
log.debug("There are no HTTP related contracts. Won't start any servers")
return
}
if (contracts.empty) {
log.warn("There are no contracts in the published JAR. This is an unusual situation " +
"that's why will start the server - maybe you know what you're doing...")
}
stubServer = stubServer.start()
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging
/**
* Factory of StubRunners. Basing on the options and passed collaborators
* downloads the stubs and returns a list of corresponding stub runners.
*/
@Slf4j
@CompileStatic
class StubRunnerFactory {
private final StubRunnerOptions stubRunnerOptions
private final StubDownloader stubDownloader
private final ContractVerifierMessaging contractVerifierMessaging
StubRunnerFactory(StubRunnerOptions stubRunnerOptions, StubDownloader stubDownloader, ContractVerifierMessaging contractVerifierMessaging) {
this.stubRunnerOptions = stubRunnerOptions
this.stubDownloader = stubDownloader
this.contractVerifierMessaging = contractVerifierMessaging
}
Collection<StubRunner> createStubsFromServiceConfiguration() {
return stubRunnerOptions.getDependencies().collect { StubConfiguration stubsConfiguration ->
Map.Entry<StubConfiguration, File> entry = stubDownloader.downloadAndUnpackStubJar(stubRunnerOptions, stubsConfiguration)
if (!entry) {
return (StubRunner) null
}
return createStubRunner(entry.key, entry.value)
}.findAll { it != null }
}
private StubRunner createStubRunner(StubConfiguration stubsConfiguration, File unzipedStubDir) {
if (!unzipedStubDir) {
return null
}
return createStubRunner(unzipedStubDir, stubsConfiguration, stubRunnerOptions)
}
private StubRunner createStubRunner(File unzippedStubsDir, StubConfiguration stubsConfiguration,
StubRunnerOptions stubRunnerOptions) {
return new StubRunner(stubRunnerOptions, unzippedStubsDir.path, stubsConfiguration, contractVerifierMessaging)
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.kohsuke.args4j.CmdLineException
import org.kohsuke.args4j.CmdLineParser
import org.kohsuke.args4j.Option
import static org.kohsuke.args4j.OptionHandlerFilter.ALL
@Slf4j
@CompileStatic
class StubRunnerMain {
@Option(name = "-sr", aliases = ['--stubRepositoryRoot'], usage = "Location of a Jar containing server where you keep your stubs (e.g. http://nexus.net/content/repositories/repository)", required = true)
private String stubRepositoryRoot
@Option(name = "-ss", aliases = ['--stubsSuffix'], usage = "Suffix for the jar containing stubs (e.g. 'stubs' if the stub jar would have a 'stubs' classifier for stubs: foobar-stubs ). Defaults to 'stubs'")
private String stubsSuffix = 'stubs'
@Option(name = "-minp", aliases = ['--minPort'], usage = "Minimal port value to be assigned to the WireMock instance. Defaults to 10000")
private Integer minPortValue = 10000
@Option(name = "-maxp", aliases = ['--maxPort'], usage = "Maximum port value to be assigned to the WireMock instance. Defaults to 15000")
private Integer maxPortValue = 15000
@Option(name = "-wo", aliases = ['--workOffline'], usage = "Switch to work offline. Defaults to 'false'")
private Boolean workOffline = Boolean.FALSE
@Option(name = "-s", aliases = ['--stubs'], usage = 'Comma separated list of Ivy representation of jars with stubs. Eg. groupid:artifactid1,groupid2:artifactid2:classifier')
private String stubs
private final Arguments arguments
StubRunnerMain(String[] args) {
CmdLineParser parser = new CmdLineParser(this)
try {
parser.parseArgument(args)
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withMinMaxPort(minPortValue, maxPortValue)
.withStubRepositoryRoot(stubRepositoryRoot)
.withWorkOffline(workOffline)
.withStubsClassifier(stubsSuffix)
.withStubs(stubs)
.build()
this.arguments = new Arguments(stubRunnerOptions)
} catch (CmdLineException e) {
printErrorMessage(e, parser)
throw e
}
}
private void printErrorMessage(CmdLineException e, CmdLineParser parser) {
System.err.println(e.getMessage())
System.err.println("java -jar stub-runner.jar [options...] ")
parser.printUsage(System.err)
System.err.println()
System.err.println("Example: java -jar stub-runner.jar ${parser.printExample(ALL)}")
}
static void main(String[] args) {
new StubRunnerMain(args).execute()
}
private void execute() {
try {
log.debug("Launching StubRunner with args: $arguments")
// TODO: Pass StubsToRun either from String or File
BatchStubRunner stubRunner = new BatchStubRunnerFactory(arguments.stubRunnerOptions).buildBatchStubRunner()
RunningStubs runningCollaborators = stubRunner.runStubs()
log.info(runningCollaborators.toString())
} catch (Exception e) {
log.error("An exception occurred while trying to execute the stubs", e)
throw e
}
}
}

View File

@@ -1,86 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.transform.ToString
/**
* Technical options related to running StubRunner
*
* Use {@class StubRunnerOptionsBuilder} to build this object.
*
* @see StubRunnerOptionsBuilder
*/
@ToString(includeNames = true)
@CompileStatic
class StubRunnerOptions {
/**
* min port value of the WireMock instance for the given collaborator
*/
final Integer minPortValue
/**
* max port value of the WireMock instance for the given collaborator
*/
final Integer maxPortValue
/**
* root URL from where the JAR with stub mappings will be downloaded
*/
final String stubRepositoryRoot
/**
* avoids local repository in dependency resolution
*/
final boolean workOffline
/**
* stub definition classifier
*/
final String stubsClassifier
final Collection<StubConfiguration> dependencies
/**
* colon separated list of ids to the desired port
*/
final Map<StubConfiguration, Integer> stubIdsToPortMapping
@PackageScope
StubRunnerOptions(Integer minPortValue, Integer maxPortValue, String stubRepositoryRoot,
boolean workOffline, String stubsClassifier, Collection<StubConfiguration> dependencies, Map<StubConfiguration, Integer> stubIdsToPortMapping) {
this.minPortValue = minPortValue
this.maxPortValue = maxPortValue
this.stubRepositoryRoot = stubRepositoryRoot
this.workOffline = workOffline
this.stubsClassifier = stubsClassifier
this.dependencies = dependencies
this.stubIdsToPortMapping = stubIdsToPortMapping
}
Integer port(StubConfiguration stubConfiguration) {
if (stubIdsToPortMapping) {
return stubIdsToPortMapping[stubConfiguration]
} else {
return null
}
}
}

View File

@@ -1,135 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import org.springframework.cloud.contract.stubrunner.util.StubsParser
@CompileStatic
class StubRunnerOptionsBuilder {
private static final String DELIMITER = ':'
private LinkedList<String> stubs = new LinkedList<>()
private Map<StubConfiguration, Integer> stubIdsToPortMapping = [:]
private Integer minPortValue = 10000
private Integer maxPortValue = 15000
private String stubRepositoryRoot
private boolean workOffline = false
private String stubsClassifier = 'stubs'
StubRunnerOptionsBuilder() {
}
StubRunnerOptionsBuilder(StubRunnerOptions options) {
withOptions(options)
}
StubRunnerOptionsBuilder withStubs(String stubs) {
addStub(stubsToList(stubs))
return this
}
StubRunnerOptionsBuilder withStubs(List<String> stubs) {
for (String stub : stubs) {
withStubs(stub)
}
return this
}
StubRunnerOptionsBuilder withMinMaxPort(Integer minPortValue, Integer maxPortValue) {
this.minPortValue = minPortValue
this.maxPortValue = maxPortValue
return this
}
StubRunnerOptionsBuilder withMinPort(int minPortValue) {
this.minPortValue = minPortValue
return this
}
StubRunnerOptionsBuilder withMaxPort(int maxPortValue) {
this.maxPortValue = maxPortValue
return this
}
StubRunnerOptionsBuilder withStubRepositoryRoot(String stubRepositoryRoot) {
this.stubRepositoryRoot = stubRepositoryRoot
return this
}
StubRunnerOptionsBuilder withWorkOffline(boolean workOffline) {
this.workOffline = workOffline
return this
}
StubRunnerOptionsBuilder withStubsClassifier(String stubsClassifier) {
this.stubsClassifier = stubsClassifier
return this
}
StubRunnerOptionsBuilder withPort(Integer port) {
String lastStub = stubs.peekLast()
println "PORT $lastStub -> $port"
addPort(lastStub + DELIMITER + port)
return this
}
StubRunnerOptionsBuilder withOptions(StubRunnerOptions options) {
this.minPortValue = options.minPortValue
this.maxPortValue = options.maxPortValue
this.stubRepositoryRoot = options.stubRepositoryRoot
this.workOffline = options.workOffline
this.stubsClassifier = options.stubsClassifier
return this
}
StubRunnerOptions build() {
return new StubRunnerOptions(minPortValue, maxPortValue, stubRepositoryRoot, workOffline, stubsClassifier, buildDependencies(), stubIdsToPortMapping)
}
private Collection<StubConfiguration> buildDependencies() {
return StubsParser.fromString(stubs, stubsClassifier)
}
private static List<String> stubsToList(String stubIdsToPortMapping) {
return stubIdsToPortMapping.split(',').collect { it } as List<String>
}
private void addStub(List<String> notations) {
for (String notation : notations) {
addStub(notation)
}
}
private void addStub(String notation) {
if (StubsParser.hasPort(notation)) {
addPort(notation)
stubs.add(StubsParser.ivyFromStringWithPort(notation))
} else {
stubs.add(notation)
}
}
private void addPort(String notation) {
putStubIdsToPortMapping(StubsParser.fromStringWithPort(notation))
}
private void putStubIdsToPortMapping(Map<StubConfiguration, Integer> stubIdsToPortMapping) {
this.stubIdsToPortMapping.putAll(stubIdsToPortMapping)
}
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.wiremock.WireMockSpring
import org.springframework.util.ClassUtils
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
@CompileStatic
@Slf4j
@PackageScope
class StubServer {
private WireMockServer wireMockServer
final StubConfiguration stubConfiguration
final Collection<WiremockMappingDescriptor> mappings
final Collection<Contract> contracts
StubServer(int port, StubConfiguration stubConfiguration, Collection<WiremockMappingDescriptor> mappings,
Collection<Contract> contracts) {
this.stubConfiguration = stubConfiguration
this.mappings = mappings
this.wireMockServer = new WireMockServer(config().port(port))
this.contracts = contracts
}
private WireMockConfiguration config() {
if (ClassUtils.isPresent('org.springframework.cloud.contract.wiremock.WireMockSpring', null)) {
return WireMockSpring.options()
}
return new WireMockConfiguration()
}
StubServer start() {
wireMockServer.start()
log.info("Started stub server for project ${stubConfiguration.toColonSeparatedDependencyNotation()} on port ${wireMockServer.port()}")
registerStubMappings()
return this
}
void stop() {
wireMockServer.stop()
}
int getPort() {
if (wireMockServer.isRunning()) {
return wireMockServer.port()
}
log.debug("The HTTP Server stub is not running... That means that the " +
"artifact is running a messaging module. Returning back -1 value of the port.")
return -1
}
URL getStubUrl() {
return new URL("http://localhost:$port")
}
private void registerStubMappings() {
WireMock wireMock = new WireMock('localhost', wireMockServer.port())
registerDefaultHealthChecks(wireMock)
registerStubs(mappings, wireMock)
}
private void registerDefaultHealthChecks(WireMock wireMock) {
registerHealthCheck(wireMock, '/ping')
registerHealthCheck(wireMock, '/health')
}
private void registerStubs(Collection<WiremockMappingDescriptor> sortedMappings, WireMock wireMock) {
sortedMappings.each { WiremockMappingDescriptor mappingDescriptor ->
try {
wireMock.register(mappingDescriptor.mapping)
log.debug("Registered stub mappings from $mappingDescriptor.descriptor")
} catch (Exception e) {
log.warn("Failed to register the stub mapping [$mappingDescriptor]", e)
}
}
}
private void registerHealthCheck(WireMock wireMock, String url, String body = 'OK') {
wireMock.register(WireMock.get(WireMock.urlEqualTo(url)).willReturn(WireMock.aResponse().withBody(body).withStatus(200)))
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import com.github.tomakehurst.wiremock.stubbing.StubMapping
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.PackageScope
import groovy.transform.ToString
/**
* Represents a single JSON file that was found in the folder with
* potential WireMock stubs
*/
@CompileStatic
@EqualsAndHashCode
@ToString(includePackage = false)
@PackageScope
class WiremockMappingDescriptor {
final File descriptor
WiremockMappingDescriptor(File mappingDescriptor) {
this.descriptor = mappingDescriptor
}
StubMapping getMapping() {
return StubMapping.buildFrom(descriptor.getText('UTF-8'))
}
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.util
import groovy.transform.CompileStatic
import org.springframework.cloud.contract.stubrunner.StubConfiguration
/**
* Utility to parse string into a list of configuration of stubs
*/
@CompileStatic
class StubsParser {
/**
* The string is expected to be a map with entry called "stubs"
* that contains a list of Strings in the format
*
* <ul>
* <li>groupid:artifactid:classifier</li>
* <li>groupid:artifactid</li>
* </ul>
*
* In the latter case the provided default stub classifier will be passed.
*
* Example:
*
* "a:b,c:d:e"
*/
static Set<StubConfiguration> fromString(String list, String defaultClassifier) {
def splitList = list.split(',').findAll { it }
return fromString(splitList, defaultClassifier)
}
static Set<StubConfiguration> fromString(Collection<String> collection, String defaultClassifier) {
return collection.findAll { it }.collect { String entry ->
def splitEntry = entry.split(':')
if (splitEntry.last().isInteger()) {
String id = entry - ":${splitEntry.last()}"
new StubConfiguration(id, defaultClassifier)
}
new StubConfiguration(entry, defaultClassifier)
} as Set
}
static Map<StubConfiguration, Integer> fromStringWithPort(String notation) {
def splitEntry = notation.split(':')
if (!splitEntry.last().isInteger()) {
return [:]
}
Integer port = splitEntry.last().toInteger()
String id = notation - ":${splitEntry.last()}"
return [(new StubConfiguration(id)): port]
}
static String ivyFromStringWithPort(String notation) {
def splitEntry = notation.split(':')
if (!splitEntry.last().isInteger()) {
return ''
}
return notation - ":${splitEntry.last()}"
}
static boolean hasPort(String notation) {
def splitEntry = notation.split(':')
return splitEntry.last().isInteger()
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.util
import groovy.transform.CompileStatic
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
/**
* Based on <a href="https://github.com/timyates/groovy-common-extensions">https://github.com/timyates/groovy-common-extensions</a>.
*
* Category for {@link File} that adds a method that allows you to unzip
* a given file to a specified location
*
*/
@CompileStatic
class ZipCategory {
/**
* Unzips this file. If the <tt>destination</tt>
* directory is not provided, it will fall back to this file's parent directory.
*
* @param self
* @param destination (optional), the destination directory where this file's content will be unzipped to.
* @return a {@link Collection} of unzipped {@link File} objects.
*/
static Collection<File> unzipTo(File self, File destination) {
checkUnzipDestination(destination)
// if destination directory is not given, we'll fall back to the parent directory of 'self'
if (destination == null) destination = new File(self.parent)
List<File> unzippedFiles = []
final ZipInputStream zipInput = new ZipInputStream(new FileInputStream(self))
zipInput.withStream {
ZipEntry entry
while (entry = zipInput.nextEntry) {
if (!entry.isDirectory()) {
final File file = new File(destination, entry.name)
file.parentFile?.mkdirs()
FileOutputStream output = new FileOutputStream(file)
output.withStream {
output << zipInput
}
unzippedFiles << file
} else {
final File dir = new File(destination, entry.name)
dir.mkdirs()
unzippedFiles << dir
}
}
}
return unzippedFiles
}
private static void checkUnzipDestination(File file) {
if (file && !file.isDirectory()) throw new IllegalArgumentException("'destination' has to be a directory.")
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.util.ArrayList;
import java.util.List;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.springframework.util.StringUtils;
public class AetherFactories {
private static final String MAVEN_LOCAL_REPOSITORY_LOCATION = "maven.repo.local";
public static RepositorySystem newRepositorySystem() {
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
return locator.getService(RepositorySystem.class);
}
public static RepositorySystemSession newSession(RepositorySystem system, boolean workOffline) {
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
session.setOffline(workOffline);
if (!workOffline) {
session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
}
LocalRepository localRepo = new LocalRepository(localRepositoryDirectory());
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
return session;
}
public static List<RemoteRepository> newRepositories(List<String> repositories) {
List<RemoteRepository> result = new ArrayList<>();
for (int index = 0; index < repositories.size(); index++) {
String repo = repositories.get(index);
if (StringUtils.hasText(repo)) {
result.add(new RemoteRepository.Builder("remote" + index, "default", repo).build());
}
}
return result;
}
public static String localRepositoryDirectory() {
return System.getProperty(MAVEN_LOCAL_REPOSITORY_LOCATION, System.getProperty("user.home") + "/.m2/repository");
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import static java.nio.file.Files.createTempDirectory;
import static org.springframework.cloud.contract.stubrunner.AetherFactories.newRepositories;
import static org.springframework.cloud.contract.stubrunner.AetherFactories.newRepositorySystem;
import static org.springframework.cloud.contract.stubrunner.AetherFactories.newSession;
import static org.springframework.cloud.contract.stubrunner.util.ZipCategory.unzipTo;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.resolution.VersionRequest;
import org.eclipse.aether.resolution.VersionResolutionException;
import org.eclipse.aether.resolution.VersionResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* @author Mariusz Smykula
*/
public class AetherStubDownloader implements StubDownloader {
private static final Logger log = LoggerFactory.getLogger(AetherStubDownloader.class);
private static final String TEMP_DIR_PREFIX = "contracts";
private static final String ARTIFACT_EXTENSION = "jar";
private static final String LATEST_ARTIFACT_VERSION = "(,]";
private static final String LATEST_VERSION_IN_IVY = "+";
private final List<RemoteRepository> remoteRepos;
private final RepositorySystem repositorySystem;
private final RepositorySystemSession session;
public AetherStubDownloader(StubRunnerOptions stubRunnerOptions) {
this.remoteRepos = remoteRepositories(stubRunnerOptions);
if (remoteRepos == null || remoteRepos.isEmpty()) {
log.error("Remote repositories for stubs are not specified!");
}
this.repositorySystem = newRepositorySystem();
this.session = newSession(this.repositorySystem, stubRunnerOptions.workOffline);
}
/**
* Used by the Maven Plugin
*
* @param repositorySystem
* @param remoteRepositories - remote artifact repositories
* @param session
* @param workOffline
*/
public AetherStubDownloader(RepositorySystem repositorySystem,
List<RemoteRepository> remoteRepositories, RepositorySystemSession session) {
this.remoteRepos = remoteRepositories;
this.repositorySystem = repositorySystem;
this.session = session;
if (remoteRepos == null || remoteRepos.isEmpty()) {
log.error("Remote remoteRepositories for stubs are not specified!");
}
}
private List<RemoteRepository> remoteRepositories(
StubRunnerOptions stubRunnerOptions) {
return newRepositories(
Arrays.asList(stubRunnerOptions.stubRepositoryRoot.split(",")));
}
private File unpackedJar(String resolvedVersion, String stubsGroup,
String stubsModule, String classifier) {
log.info("Resolved version is" + resolvedVersion);
if (!StringUtils.hasText(resolvedVersion)) {
log.warn("Stub for group [" + stubsGroup + "] module [" + stubsModule
+ "] and classifier [" + classifier + "] not found in "
+ remoteRepos);
return null;
}
Artifact artifact = new DefaultArtifact(stubsGroup, stubsModule, classifier,
ARTIFACT_EXTENSION, resolvedVersion);
ArtifactRequest request = new ArtifactRequest(artifact, remoteRepos, null);
log.info("Resolving artifact " + artifact
+ " using remote repositories " + remoteRepos);
try {
ArtifactResult result = repositorySystem.resolveArtifact(session, request);
log.info("Resolved artifact " + artifact + "to "
+ result.getArtifact().getFile());
File temporaryFile = unpackStubJarToATemporaryFolder(
result.getArtifact().getFile().toURI());
log.info("Unpacked file to [" + temporaryFile + "]");
return temporaryFile;
}
catch (Exception e) {
log.warn(
"Exception occured while trying to download a stub for group ["
+ stubsGroup + "] module [" + stubsModule
+ "] and classifier [" + classifier + "] in " + remoteRepos,
e);
return null;
}
}
private String getVersion(String stubsGroup, String stubsModule, String version,
String classifier) {
if (!StringUtils.hasText(version) || LATEST_VERSION_IN_IVY.equals(version)) {
log.info("Desired version is " + version
+ " - will try to resolve the latest version");
return resolveHighestArtifactVersion(stubsGroup, stubsModule, classifier);
}
log.info("Will try to resolve version " + version);
return resolveArtifactVersion(stubsGroup, stubsModule, version, classifier);
}
@Override
public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
StubRunnerOptions options, StubConfiguration stubConfiguration) {
String version = getVersion(stubConfiguration.groupId,
stubConfiguration.artifactId, stubConfiguration.version,
stubConfiguration.classifier);
File unpackedJar = unpackedJar(version, stubConfiguration.groupId,
stubConfiguration.artifactId, stubConfiguration.classifier);
if (unpackedJar == null) {
return null;
}
return new AbstractMap.SimpleEntry<StubConfiguration, File>(new StubConfiguration(
stubConfiguration.groupId, stubConfiguration.artifactId, version,
stubConfiguration.classifier), unpackedJar);
}
private String resolveHighestArtifactVersion(String stubsGroup, String stubsModule,
String classifier) {
Artifact artifact = new DefaultArtifact(stubsGroup, stubsModule, classifier,
ARTIFACT_EXTENSION, LATEST_ARTIFACT_VERSION);
VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact,
remoteRepos, null);
VersionRangeResult rangeResult;
try {
rangeResult = repositorySystem.resolveVersionRange(session,
versionRangeRequest);
}
catch (VersionRangeResolutionException e) {
throw new IllegalStateException("Cannot resolve version range", e);
}
if (rangeResult.getHighestVersion() == null) {
log.error("Version was not resolved!");
}
return rangeResult.getHighestVersion() == null ? null : rangeResult.getHighestVersion().toString();
}
private String resolveArtifactVersion(String stubsGroup, String stubsModule,
String version, String classifier) {
Artifact artifact = new DefaultArtifact(stubsGroup, stubsModule, classifier,
ARTIFACT_EXTENSION, version);
VersionRequest versionRequest = new VersionRequest(artifact, remoteRepos, null);
VersionResult versionResult;
try {
versionResult = repositorySystem.resolveVersion(session, versionRequest);
}
catch (VersionResolutionException e) {
throw new IllegalStateException("Cannot resolve version", e);
}
return versionResult.getVersion() == null ? null : versionResult.getVersion();
}
private static File unpackStubJarToATemporaryFolder(URI stubJarUri) {
File tmpDirWhereStubsWillBeUnzipped;
try {
tmpDirWhereStubsWillBeUnzipped = createTempDirectory(TEMP_DIR_PREFIX)
.toFile();
}
catch (IOException e) {
throw new IllegalStateException("Cannot create tmp dir with prefix: [" + TEMP_DIR_PREFIX + "]", e);
}
tmpDirWhereStubsWillBeUnzipped.deleteOnExit();
log.info("Unpacking stub from JAR [URI: " + stubJarUri + "]");
unzipTo(new File(stubJarUri), tmpDirWhereStubsWillBeUnzipped);
return tmpDirWhereStubsWillBeUnzipped;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
/**
* Arguments passed to the {@link StubRunner} application
*
* @see StubRunner
*/
public class Arguments {
final private StubRunnerOptions stubRunnerOptions;
final private String context;
final private String repositoryPath;
final private StubConfiguration stub;
Arguments(StubRunnerOptions stubRunnerOptions) {
this(stubRunnerOptions, "", null);
}
Arguments(StubRunnerOptions stubRunnerOptions, String repositoryPath,
StubConfiguration stub) {
this.stubRunnerOptions = stubRunnerOptions;
this.repositoryPath = repositoryPath == null ? "" : repositoryPath;
this.context = null; // eh?
this.stub = stub;
}
public StubRunnerOptions getStubRunnerOptions() {
return stubRunnerOptions;
}
public String getContext() {
return context;
}
public String getRepositoryPath() {
return repositoryPath;
}
public StubConfiguration getStub() {
return stub;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tries to execute a closure with an available port from the given range
*/
public class AvailablePortScanner {
private static final Logger log = LoggerFactory.getLogger(AvailablePortScanner.class);
private static final int MAX_RETRY_COUNT = 1000;
private final int minPortNumber;
private final int maxPortNumber;
private final int maxRetryCount;
AvailablePortScanner(int minPortNumber, int maxPortNumber) {
this(minPortNumber, maxPortNumber, MAX_RETRY_COUNT);
}
AvailablePortScanner(int minPortNumber, int maxPortNumber, int maxRetryCount) {
checkPortRanges(minPortNumber, maxPortNumber);
this.minPortNumber = minPortNumber;
this.maxPortNumber = maxPortNumber;
this.maxRetryCount = maxRetryCount;
}
private void checkPortRanges(int minPortNumber, int maxPortNumber) {
if (minPortNumber > maxPortNumber) {
throw new InvalidPortRange(minPortNumber, maxPortNumber);
}
}
public <T> T tryToExecuteWithFreePort(PortCallback<T> closure) {
for (int i = 0; i < maxRetryCount; i++) {
try {
int numberOfPortsToBind = maxPortNumber - minPortNumber + 1;
int portToScan = new Random().nextInt(numberOfPortsToBind)
+ minPortNumber;
checkIfPortIsAvailable(portToScan);
return executeLogicForAvailablePort(portToScan, closure);
}
catch (IOException exception) {
log.debug("Failed to execute callback (try: " + i + "/" + maxRetryCount
+ ")", exception);
}
}
throw new NoPortAvailableException(minPortNumber, maxPortNumber);
}
private <T> T executeLogicForAvailablePort(int portToScan, PortCallback<T> closure) throws IOException {
log.debug("Trying to execute closure with port [$portToScan]");
return closure.call(portToScan);
}
private void checkIfPortIsAvailable(int portToScan) throws IOException {
ServerSocket socket = null;
try {
socket = new ServerSocket(portToScan);
}
finally {
if (socket != null) {
socket.close();
}
}
}
@SuppressWarnings("serial")
static class NoPortAvailableException extends RuntimeException {
protected NoPortAvailableException(int lowerBound, int upperBound) {
super("Could not find available port in range " + lowerBound + ":" + upperBound);
}
}
@SuppressWarnings("serial")
static class InvalidPortRange extends RuntimeException {
protected InvalidPortRange(int lowerBound, int upperBound) {
super("Invalid bounds exceptions, min port [" + lowerBound
+ "] is greater to max port [" + upperBound + "]");
}
}
public static interface PortCallback<T> {
T call(int port) throws IOException;
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.cloud.contract.spec.Contract;
/**
* Manages lifecycle of multiple {@link StubRunner} instances.
*
* @see StubRunner
*/
public class BatchStubRunner implements StubRunning {
private final Iterable<StubRunner> stubRunners;
public BatchStubRunner(Iterable<StubRunner> stubRunners) {
this.stubRunners = stubRunners;
}
@Override
public RunningStubs runStubs() {
Map<StubConfiguration, Integer> map = new LinkedHashMap<>();
for (StubRunner value : stubRunners) {
RunningStubs runningStubs = value.runStubs();
map.putAll(runningStubs.validNamesAndPorts());
}
return new RunningStubs(map);
}
@Override
public URL findStubUrl(String groupId, String artifactId) {
for (StubRunner stubRunner : stubRunners) {
URL url = stubRunner.findStubUrl(groupId, artifactId);
if (url != null) {
return url;
}
}
return null;
}
@Override
public URL findStubUrl(String ivyNotation) {
String[] splitString = ivyNotation.split(":");
if (splitString.length > 3) {
throw new IllegalArgumentException(ivyNotation + " is invalid");
}
else if (splitString.length == 2) {
return findStubUrl(splitString[0], splitString[1]);
}
return findStubUrl(null, splitString[0]);
}
@Override
public RunningStubs findAllRunningStubs() {
Collection<RunningStubs> running = new LinkedHashSet<>();
for (StubRunner stubRunner : stubRunners) {
running.add(stubRunner.findAllRunningStubs());
}
return new RunningStubs(running);
}
@Override
public Map<StubConfiguration, Collection<Contract>> getContracts() {
Map<StubConfiguration, Collection<Contract>> map = new LinkedHashMap<>();
for (StubRunner stubRunner : stubRunners) {
for (Entry<StubConfiguration, Collection<Contract>> entry : stubRunner
.getContracts().entrySet()) {
if (map.containsKey(entry.getKey())) {
map.get(entry.getKey()).addAll(entry.getValue());
}
else {
map.put(entry.getKey(), new LinkedHashSet<>(entry.getValue()));
}
}
}
return map;
}
@Override
public boolean trigger(String ivyNotation, String labelName) {
boolean success = false;
for (StubRunner stubRunner : stubRunners) {
if (stubRunner.trigger(ivyNotation, labelName)) {
success = true;
}
}
if (!success) {
throw new IllegalArgumentException("No label with name [" + labelName
+ "] for " + "dependency [" + ivyNotation
+ "] was found. Here you have the list of dependencies "
+ "and their labels [" + ivyToLabels() + "]");
}
return success;
}
private String ivyToLabels() {
StringBuilder builder = new StringBuilder();
for (Entry<String, Collection<String>> entry : labels().entrySet()) {
if (builder.length() > 0) {
builder.append("\n");
}
builder.append("Dependency [").append(entry.getKey()).append("] has labels ")
.append(entry.getValue());
}
return builder.toString();
}
@Override
public boolean trigger(String labelName) {
boolean success = false;
for (StubRunner stubRunner : stubRunners) {
if (stubRunner.trigger(labelName)) {
success = true;
}
}
if (!success) {
throw new IllegalArgumentException(
"No label with name [" + labelName + "] was found. "
+ "Here you have the list of dependencies and their labels ["
+ ivyToLabels() + "]");
}
return success;
}
@Override
public boolean trigger() {
boolean success = false;
for (StubRunner stubRunner : stubRunners) {
if (stubRunner.trigger()) {
success = true;
}
}
return success;
}
@Override
public Map<String, Collection<String>> labels() {
Map<String, Collection<String>> map = new LinkedHashMap<>();
for (StubRunner stubRunner : stubRunners) {
for (Entry<String, Collection<String>> entry : stubRunner.labels()
.entrySet()) {
if (map.containsKey(entry.getKey())) {
map.get(entry.getKey()).addAll(entry.getValue());
}
else {
map.put(entry.getKey(), new LinkedHashSet<>(entry.getValue()));
}
}
}
return map;
}
@Override
public void close() throws IOException {
for (StubRunner stubRunner : stubRunners) {
stubRunner.close();
}
}
}

View File

@@ -14,11 +14,10 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
package org.springframework.cloud.contract.stubrunner;
import groovy.transform.CompileStatic
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging
import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierMessaging
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging;
import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierMessaging;
/**
* Manages lifecycle of multiple {@link StubRunner} instances.
@@ -26,34 +25,33 @@ import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVe
* @see StubRunner
* @see BatchStubRunner
*/
@CompileStatic
class BatchStubRunnerFactory {
public class BatchStubRunnerFactory {
private final StubRunnerOptions stubRunnerOptions
private final StubDownloader stubDownloader
private final ContractVerifierMessaging contractVerifierMessaging
private final StubRunnerOptions stubRunnerOptions;
private final StubDownloader stubDownloader;
private final ContractVerifierMessaging contractVerifierMessaging;
BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions) {
this(stubRunnerOptions, new AetherStubDownloader(stubRunnerOptions), new NoOpContractVerifierMessaging())
public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions) {
this(stubRunnerOptions, new AetherStubDownloader(stubRunnerOptions), new NoOpContractVerifierMessaging());
}
BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, ContractVerifierMessaging contractVerifierMessaging) {
this(stubRunnerOptions, new AetherStubDownloader(stubRunnerOptions), contractVerifierMessaging)
public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, ContractVerifierMessaging contractVerifierMessaging) {
this(stubRunnerOptions, new AetherStubDownloader(stubRunnerOptions), contractVerifierMessaging);
}
BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, StubDownloader stubDownloader) {
this(stubRunnerOptions, stubDownloader, new NoOpContractVerifierMessaging())
public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, StubDownloader stubDownloader) {
this(stubRunnerOptions, stubDownloader, new NoOpContractVerifierMessaging());
}
BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, StubDownloader stubDownloader, ContractVerifierMessaging contractVerifierMessaging) {
this.stubRunnerOptions = stubRunnerOptions
this.stubDownloader = stubDownloader
this.contractVerifierMessaging = contractVerifierMessaging
public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, StubDownloader stubDownloader, ContractVerifierMessaging contractVerifierMessaging) {
this.stubRunnerOptions = stubRunnerOptions;
this.stubDownloader = stubDownloader;
this.contractVerifierMessaging = contractVerifierMessaging;
}
BatchStubRunner buildBatchStubRunner() {
StubRunnerFactory stubRunnerFactory = new StubRunnerFactory(stubRunnerOptions, stubDownloader, contractVerifierMessaging)
return new BatchStubRunner(stubRunnerFactory.createStubsFromServiceConfiguration())
public BatchStubRunner buildBatchStubRunner() {
StubRunnerFactory stubRunnerFactory = new StubRunnerFactory(stubRunnerOptions, stubDownloader, contractVerifierMessaging);
return new BatchStubRunner(stubRunnerFactory.createStubsFromServiceConfiguration());
}
}

View File

@@ -14,26 +14,22 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
package org.springframework.cloud.contract.stubrunner;
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.Contract;
/**
* @author Marcin Grzejszczak
*/
@PackageScope
@CompileStatic
class GroovyDslWrapper {
@Delegate final Contract groovyDsl
final Contract groovyDsl;
GroovyDslWrapper(Contract groovyDsl) {
this.groovyDsl = groovyDsl
this.groovyDsl = groovyDsl;
}
boolean hasHttpPart() {
return groovyDsl.request
return groovyDsl.getRequest() != null;
}
}

View File

@@ -14,15 +14,13 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
import groovy.transform.InheritConstructors
package org.springframework.cloud.contract.stubrunner;
/**
* Exception thrown when message is not matched
*
* @author Marcin Grzejszczak
*/
@InheritConstructors
@SuppressWarnings("serial")
class MessageNotMatchingException extends RuntimeException {
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Structure representing executed stubs. Contains the configuration of each stub together
* with the port on which its executed.
*/
public class RunningStubs {
final private Map<StubConfiguration, Integer> namesAndPorts = new LinkedHashMap<>();
public RunningStubs(Map<StubConfiguration, Integer> map) {
this.namesAndPorts.putAll(map);
}
public RunningStubs(Collection<RunningStubs> runningStubs) {
for (RunningStubs it : runningStubs) {
this.namesAndPorts.putAll(it.namesAndPorts);
}
}
public Integer getPort(String artifactId) {
return getEntry(artifactId) == null ? null : getEntry(artifactId).getValue();
}
public Map.Entry<StubConfiguration, Integer> getEntry(String artifactId) {
for (Entry<StubConfiguration, Integer> it : namesAndPorts.entrySet()) {
if (it.getKey().matchesIvyNotation(artifactId)) {
return it;
}
}
return null;
}
public Integer getPort(String groupId, String artifactId) {
for (Entry<StubConfiguration, Integer> it : namesAndPorts.entrySet()) {
if (it.getKey().matchesIvyNotation(groupId + ":" + artifactId)) {
return it.getValue();
}
}
return null;
}
public boolean isPresent(String artifactId) {
return getEntry(artifactId) != null;
}
public boolean isPresent(String groupId, String artifactId) {
return getPort(groupId, artifactId) != null;
}
public Set<StubConfiguration> getAllServices() {
return namesAndPorts.keySet();
}
public Set<String> getAllServicesNames() {
Set<String> result = new LinkedHashSet<>();
for (Entry<StubConfiguration, Integer> it : namesAndPorts.entrySet()) {
result.add(it.getKey().artifactId);
}
return result;
}
public Map<String, Integer> toIvyToPortMapping() {
Map<String, Integer> result = new LinkedHashMap<>();
for (Entry<StubConfiguration, Integer> it : namesAndPorts.entrySet()) {
result.put(it.getKey().toColonSeparatedDependencyNotation(), it.getValue());
}
return result;
}
public Map<StubConfiguration, Integer> validNamesAndPorts() {
Map<StubConfiguration, Integer> result = new LinkedHashMap<>();
for (Entry<StubConfiguration, Integer> it : namesAndPorts.entrySet()) {
if (it.getValue() != null && it.getValue() >= 0) {
result.put(it.getKey(), it.getValue());
}
}
return result;
}
@Override
public String toString() {
return "RunningStubs [namesAndPorts=" + namesAndPorts + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((namesAndPorts == null) ? 0 : namesAndPorts.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RunningStubs other = (RunningStubs) obj;
if (namesAndPorts == null) {
if (other.namesAndPorts != null)
return false;
}
else if (!namesAndPorts.equals(other.namesAndPorts))
return false;
return true;
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.StringUtils;
/**
* Represents a configuration of a single stub. The stub can be described by
* groupId:artifactId:classifier notation
*/
public class StubConfiguration {
private static final String STUB_COLON_DELIMITER = ":";
private static final String DEFAULT_VERSION = "+";
public static final String DEFAULT_CLASSIFIER = "stubs";
final String groupId;
final String artifactId;
final String version;
final String classifier;
public StubConfiguration(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.classifier = DEFAULT_CLASSIFIER;
}
public StubConfiguration(String groupId, String artifactId, String version,
String classifier) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.classifier = classifier;
}
public StubConfiguration(String stubPath, String defaultClassifier) {
String[] parsedPath = parsedPathEmptyByDefault(stubPath, STUB_COLON_DELIMITER,
defaultClassifier);
this.groupId = parsedPath[0];
this.artifactId = parsedPath[1];
this.version = parsedPath[2];
this.classifier = parsedPath[3];
}
public StubConfiguration(String stubPath) {
String[] parsedPath = parsedPathEmptyByDefault(stubPath, STUB_COLON_DELIMITER,
DEFAULT_CLASSIFIER);
this.groupId = parsedPath[0];
this.artifactId = parsedPath[1];
this.version = parsedPath[2];
this.classifier = parsedPath[3];
}
private String[] parsedPathEmptyByDefault(String path, String delimiter,
String defaultClassifier) {
String[] splitPath = path.split(delimiter);
String stubsGroupId = "";
String stubsArtifactId = "";
String stubsVersion = "";
String stubsClassifier = "";
if (splitPath.length >= 2) {
stubsGroupId = splitPath[0];
stubsArtifactId = splitPath[1];
stubsVersion = splitPath.length >= 3 ? splitPath[2] : DEFAULT_VERSION;
stubsClassifier = splitPath.length == 4 ? splitPath[3] : defaultClassifier;
}
return new String[] { stubsGroupId, stubsArtifactId, stubsVersion,
stubsClassifier };
}
private boolean isDefined() {
return StringUtils.hasText(groupId) && StringUtils.hasText(this.artifactId);
}
public String toColonSeparatedDependencyNotation() {
if (!isDefined()) {
return "";
}
return StringUtils.arrayToDelimitedString(
new String[] { groupId, artifactId, version, classifier },
STUB_COLON_DELIMITER);
}
public boolean groupIdAndArtifactMatches(String ivyNotationAsString) {
String[] parts = ivyNotationFrom(ivyNotationAsString);
String groupId = parts[0];
String artifactId = parts[1];
if (groupId == null) {
return this.artifactId.equals(artifactId);
}
return this.groupId.equals(groupId) && this.artifactId.equals(artifactId);
}
public String getGroupId() {
return groupId;
}
public String getArtifactId() {
return artifactId;
}
public String getClassifier() {
return classifier;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((artifactId == null) ? 0 : artifactId.hashCode());
result = prime * result + ((groupId == null) ? 0 : groupId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StubConfiguration other = (StubConfiguration) obj;
if (artifactId == null) {
if (other.artifactId != null)
return false;
}
else if (!artifactId.equals(other.artifactId))
return false;
if (groupId == null) {
if (other.groupId != null)
return false;
}
else if (!groupId.equals(other.groupId))
return false;
return true;
}
public boolean matchesIvyNotation(String ivyNotationAsString) {
String[] strings = ivyNotationAsString.split(":");
if (strings.length == 1) {
return artifactId.equals(ivyNotationAsString);
}
else if (strings.length == 2) {
return groupId.equals(strings[0]) && artifactId.equals(strings[1]);
}
else if (strings.length == 3) {
return groupId.equals(strings[0]) && artifactId.equals(strings[1])
&& (strings[2].equals(DEFAULT_VERSION) || version.equals(strings[2]));
}
return groupId.equals(strings[0]) && artifactId.equals(strings[1])
&& (strings[2].equals(DEFAULT_VERSION) || version.equals(strings[2]))
&& classifier.equals(strings[3]);
}
private String[] ivyNotationFrom(String ivyNotation) {
String[] splitString = ivyNotation.split(":");
if (splitString.length == 1) {
// assuming that ivy notation represents artifactId only
return new String[] { null, splitString[0] };
}
return new String[] { splitString[0], splitString[1] };
}
@Override
public String toString() {
return toColonSeparatedDependencyNotation();
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.util.List;
import org.springframework.cloud.contract.spec.Contract;
/**
* @author Marcin Grzejszczak
*/
public class StubData {
final Integer port;
final List<Contract> contracts;
public StubData(Integer port, List<Contract> contracts) {
this.port = port;
this.contracts = contracts;
}
public Integer getPort() {
return port;
}
public List<Contract> getContracts() {
return contracts;
}
@Override
public String toString() {
return "StubData [port=" + port + ", contracts=" + contracts + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((contracts == null) ? 0 : contracts.hashCode());
result = prime * result + ((port == null) ? 0 : port.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StubData other = (StubData) obj;
if (contracts == null) {
if (other.contracts != null)
return false;
}
else if (!contracts.equals(other.contracts))
return false;
if (port == null) {
if (other.port != null)
return false;
}
else if (!port.equals(other.port))
return false;
return true;
}
}

View File

@@ -14,15 +14,18 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
package org.springframework.cloud.contract.stubrunner;
interface StubDownloader {
import java.io.File;
import java.util.Map;
public interface StubDownloader {
/**
* Returns a mapping of updated StubConfiguration (it will contain the resolved version) and the location of the downloaded JAR.
* If there was no artifact this method will return {@code null}.
*/
Map.Entry<StubConfiguration,File> downloadAndUnpackStubJar(StubRunnerOptions options, StubConfiguration stubConfiguration)
Map.Entry<StubConfiguration,File> downloadAndUnpackStubJar(StubRunnerOptions options, StubConfiguration stubConfiguration);
}

View File

@@ -14,11 +14,15 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
package org.springframework.cloud.contract.stubrunner;
import org.springframework.cloud.contract.spec.Contract
import java.net.URL;
import java.util.Collection;
import java.util.Map;
interface StubFinder extends StubTrigger {
import org.springframework.cloud.contract.spec.Contract;
public interface StubFinder extends StubTrigger {
/**
* For the given groupId and artifactId tries to find the matching
* URL of the running stub.
@@ -26,7 +30,7 @@ interface StubFinder extends StubTrigger {
* @param groupId - might be null. In that case a search only via artifactId takes place
* @return URL of a running stub or null if not found
*/
URL findStubUrl(String groupId, String artifactId)
URL findStubUrl(String groupId, String artifactId);
/**
* For the given Ivy notation {@code groupId:artifactId} tries to find the matching
@@ -35,15 +39,15 @@ interface StubFinder extends StubTrigger {
* @param ivyNotation - Ivy representation of the Maven artifact
* @return URL of a running stub or null if not found
*/
URL findStubUrl(String ivyNotation)
URL findStubUrl(String ivyNotation);
/**
* Returns all running stubs
*/
RunningStubs findAllRunningStubs()
RunningStubs findAllRunningStubs();
/**
* Returns the list of Contracts
*/
Map<StubConfiguration, Collection<Contract>> getContracts()
Map<StubConfiguration, Collection<Contract>> getContracts();
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter;
/**
* Wraps the folder with WireMock mappings.
*/
public class StubRepository {
private static final Logger log = LoggerFactory.getLogger(StubRepository.class);
private final File path;
final List<WiremockMappingDescriptor> projectDescriptors;
final Collection<Contract> contracts;
public StubRepository(File repository) {
if (!repository.isDirectory()) {
throw new IllegalArgumentException(
"Missing descriptor repository under path [" + repository + "]");
}
this.path = repository;
this.projectDescriptors = projectDescriptors();
this.contracts = contracts();
}
public File getPath() {
return path;
}
public List<WiremockMappingDescriptor> getProjectDescriptors() {
return projectDescriptors;
}
public Collection<Contract> getContracts() {
return contracts;
}
/**
* Returns a list of {@link Contract}
*/
private Collection<Contract> contracts() {
List<Contract> contracts = new ArrayList<>();
contracts.addAll(contractDescriptors());
return contracts;
}
/**
* Returns the list of WireMock JSON files wrapped in
* {@link WiremockMappingDescriptor}
*/
private List<WiremockMappingDescriptor> projectDescriptors() {
List<WiremockMappingDescriptor> mappingDescriptors = new ArrayList<>();
mappingDescriptors.addAll(contextDescriptors());
return mappingDescriptors;
}
private List<WiremockMappingDescriptor> contextDescriptors() {
return path.exists() ? collectMappingDescriptors(path)
: Collections.<WiremockMappingDescriptor>emptyList();
}
private List<WiremockMappingDescriptor> collectMappingDescriptors(
File descriptorsDirectory) {
final List<WiremockMappingDescriptor> mappingDescriptors = new ArrayList<>();
try {
Files.walkFileTree(Paths.get(descriptorsDirectory.toURI()),
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path,
BasicFileAttributes attrs) throws IOException {
File file = path.toFile();
if (isMappingDescriptor(file)) {
mappingDescriptors
.add(new WiremockMappingDescriptor(file));
}
return super.visitFile(path, attrs);
}
});
}
catch (IOException e) {
log.warn("Exception occurred while trying to parse file", e);
}
return mappingDescriptors;
}
private Collection<Contract> contractDescriptors() {
return (path.exists() ? collectContractDescriptors(path)
: Collections.<Contract>emptySet());
}
private Collection<Contract> collectContractDescriptors(File descriptorsDirectory) {
final List<Contract> mappingDescriptors = new ArrayList<>();
try {
Files.walkFileTree(Paths.get(descriptorsDirectory.toURI()),
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path,
BasicFileAttributes attrs) throws IOException {
File file = path.toFile();
if (isContractDescriptor(file)) {
mappingDescriptors
.add(ContractVerifierDslConverter.convert(file));
}
return super.visitFile(path, attrs);
}
});
}
catch (IOException e) {
log.warn("Exception occurred while trying to parse file", e);
}
return mappingDescriptors;
}
private static boolean isMappingDescriptor(File file) {
return file.isFile() && file.getName().endsWith(".json");
}
private static boolean isContractDescriptor(File file) {
// TODO: Consider script injections implications...
return file.isFile() && file.getName().endsWith(".groovy");
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging;
import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierMessaging;
/**
* Represents a single instance of ready-to-run stubs. Can run the stubs and then will
* return the name of the collaborator together with its URI. Can also be queried if the
* current groupid and artifactid are matching the corresponding running stub.
*/
public class StubRunner implements StubRunning {
private final StubRepository stubRepository;
private final StubConfiguration stubsConfiguration;
private final StubRunnerOptions stubRunnerOptions;
private final StubRunnerExecutor localStubRunner;
@Deprecated
StubRunner(Arguments arguments) {
this(arguments.getStubRunnerOptions(), arguments.getRepositoryPath(),
arguments.getStub());
}
public StubRunner(StubRunnerOptions stubRunnerOptions, String repositoryPath,
StubConfiguration stubsConfiguration) {
this(stubRunnerOptions, repositoryPath, stubsConfiguration,
new NoOpContractVerifierMessaging());
}
public StubRunner(StubRunnerOptions stubRunnerOptions, String repositoryPath,
StubConfiguration stubsConfiguration,
ContractVerifierMessaging<?, ?> contractVerifierMessaging) {
this.stubsConfiguration = stubsConfiguration;
this.stubRunnerOptions = stubRunnerOptions;
this.stubRepository = new StubRepository(new File(repositoryPath));
AvailablePortScanner portScanner = new AvailablePortScanner(
stubRunnerOptions.getMinPortValue(), stubRunnerOptions.getMaxPortValue());
this.localStubRunner = new StubRunnerExecutor(portScanner, (ContractVerifierMessaging<String, Object>) contractVerifierMessaging);
}
@Override
public RunningStubs runStubs() {
registerShutdownHook();
return localStubRunner.runStubs(stubRunnerOptions, stubRepository,
stubsConfiguration);
}
@Override
public URL findStubUrl(String groupId, String artifactId) {
return localStubRunner.findStubUrl(groupId, artifactId);
}
@Override
public URL findStubUrl(String ivyNotation) {
String[] splitString = ivyNotation.split(":");
if (splitString.length == 1) {
// assuming that ivy notation represents artifactId only
return findStubUrl(null, splitString[0]);
}
return findStubUrl(splitString[0], splitString[1]);
}
@Override
public RunningStubs findAllRunningStubs() {
return localStubRunner.findAllRunningStubs();
}
@Override
public Map<StubConfiguration, Collection<Contract>> getContracts() {
return localStubRunner.getContracts();
}
@Override
public boolean trigger(String ivyNotation, String labelName) {
return localStubRunner.trigger(ivyNotation, labelName);
}
@Override
public boolean trigger(String labelName) {
return localStubRunner.trigger(labelName);
}
@Override
public boolean trigger() {
return localStubRunner.trigger();
}
@Override
public Map<String, Collection<String>> labels() {
return localStubRunner.labels();
}
private void registerShutdownHook() {
Runnable stopAllServers = new Runnable() {
@Override
public void run() {
try {
close();
}
catch (IOException e) {
}
}
};
Runtime.getRuntime().addShutdownHook(new Thread(stopAllServers));
}
@Override
public void close() throws IOException {
if (localStubRunner != null) {
localStubRunner.shutdown();
}
}
}

View File

@@ -0,0 +1,234 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.Headers;
import org.springframework.cloud.contract.spec.internal.OutputMessage;
import org.springframework.cloud.contract.stubrunner.AvailablePortScanner.PortCallback;
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessage;
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging;
import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierMessaging;
import org.springframework.cloud.contract.verifier.util.BodyExtractor;
import groovy.json.JsonOutput;
/**
* Runs stubs for a particular {@link StubServer}
*/
public class StubRunnerExecutor implements StubFinder {
private static final Logger log = LoggerFactory.getLogger(StubRunnerExecutor.class);
private final AvailablePortScanner portScanner;
private final ContractVerifierMessaging<String, Object> contractVerifierMessaging;
private StubServer stubServer;
public StubRunnerExecutor(AvailablePortScanner portScanner,
ContractVerifierMessaging<String, Object> contractVerifierMessaging) {
this.portScanner = portScanner;
this.contractVerifierMessaging = contractVerifierMessaging;
}
public StubRunnerExecutor(AvailablePortScanner portScanner) {
this(portScanner, new NoOpContractVerifierMessaging());
}
public RunningStubs runStubs(StubRunnerOptions stubRunnerOptions,
StubRepository repository, StubConfiguration stubConfiguration) {
startStubServers(stubRunnerOptions, stubConfiguration, repository);
RunningStubs runningCollaborators = new RunningStubs(Collections
.singletonMap(stubServer.getStubConfiguration(), stubServer.getPort()));
log.info("All stubs are now running " + runningCollaborators.toString());
return runningCollaborators;
}
public void shutdown() {
if (stubServer != null) {
stubServer.stop();
}
}
@Override
public URL findStubUrl(String groupId, String artifactId) {
if (groupId == null) {
return returnStubUrlIfMatches(
artifactId.equals(stubServer.stubConfiguration.artifactId));
}
return returnStubUrlIfMatches(
artifactId.equals(stubServer.stubConfiguration.artifactId)
&& groupId.equals(stubServer.stubConfiguration.groupId));
}
@Override
public URL findStubUrl(String ivyNotation) {
String[] splitString = ivyNotation.split(":");
if (splitString.length == 1) {
throw new IllegalArgumentException("$ivyNotation is invalid");
}
return findStubUrl(splitString[0], splitString[1]);
}
@Override
public RunningStubs findAllRunningStubs() {
return new RunningStubs(Collections.singletonMap(stubServer.stubConfiguration,
stubServer.getPort()));
}
@Override
public Map<StubConfiguration, Collection<Contract>> getContracts() {
return Collections.singletonMap(stubServer.stubConfiguration,
stubServer.getContracts());
}
@Override
public boolean trigger(String ivyNotationAsString, String labelName) {
Collection<Contract> matchingContracts = new ArrayList<>();
for (Entry<StubConfiguration, Collection<Contract>> it : getContracts()
.entrySet()) {
if (it.getKey().groupIdAndArtifactMatches(ivyNotationAsString)) {
matchingContracts.addAll(it.getValue());
}
}
;
return triggerForDsls(matchingContracts, labelName);
}
@Override
public boolean trigger(String labelName) {
Collection<Contract> matchingContracts = new ArrayList<>();
for (Collection<Contract> it : getContracts().values()) {
matchingContracts.addAll(it);
}
return triggerForDsls(matchingContracts, labelName);
}
private boolean triggerForDsls(Collection<Contract> dsls, String labelName) {
Collection<Contract> matchingDsls = new ArrayList<>();
for (Contract contract : dsls) {
if (labelName.equals(contract.getLabel())) {
matchingDsls.add(contract);
}
}
if (matchingDsls.isEmpty()) {
return false;
}
for (Contract contract : matchingDsls) {
sendMessageIfApplicable(contract);
}
return true;
}
@Override
public boolean trigger() {
Collection<Contract> matchingContracts = new ArrayList<>();
for (Collection<Contract> it : getContracts().values()) {
matchingContracts.addAll(it);
}
for (Contract contract : matchingContracts) {
sendMessageIfApplicable(contract);
}
return true;
}
@Override
public Map<String, Collection<String>> labels() {
Map<String, Collection<String>> labels = new LinkedHashMap<>();
for (Entry<StubConfiguration, Collection<Contract>> it : getContracts()
.entrySet()) {
Collection<String> values = new ArrayList<>();
for (Contract contract : it.getValue()) {
values.add(contract.getLabel());
}
labels.put(it.getKey().toColonSeparatedDependencyNotation(), values);
}
return labels;
}
private void sendMessageIfApplicable(Contract groovyDsl) {
OutputMessage outputMessage = groovyDsl.getOutputMessage();
if (outputMessage == null) {
return;
}
DslProperty<?> body = outputMessage == null ? null : outputMessage.getBody();
Headers headers = outputMessage == null ? null : outputMessage.getHeaders();
ContractVerifierMessage<String, Object> message = contractVerifierMessaging
.create(JsonOutput.toJson(BodyExtractor.extractClientValueFromBody(
body == null ? null : body.getClientValue())),
headers == null ? null : headers.asStubSideMap());
contractVerifierMessaging.send(message,
outputMessage.getSentTo().getClientValue());
}
private URL returnStubUrlIfMatches(boolean condition) {
return condition ? stubServer.getStubUrl() : null;
}
private void startStubServers(StubRunnerOptions stubRunnerOptions,
final StubConfiguration stubConfiguration, StubRepository repository) {
final List<WiremockMappingDescriptor> mappings = repository
.getProjectDescriptors();
final Collection<Contract> contracts = repository.contracts;
Integer port = stubRunnerOptions.port(stubConfiguration);
if (port != null && port >= 0) {
stubServer = new StubServer(port, stubConfiguration, mappings, contracts);
}
else {
stubServer = portScanner
.tryToExecuteWithFreePort(new PortCallback<StubServer>() {
@Override
public StubServer call(int availablePort) {
return new StubServer(availablePort, stubConfiguration,
mappings, contracts);
}
});
}
if (!contracts.isEmpty() && !hasRequest(contracts)) {
log.debug("There are no HTTP related contracts. Won't start any servers");
return;
}
if (contracts.isEmpty()) {
log.warn(
"There are no contracts in the published JAR. This is an unusual situation "
+ "that's why will start the server - maybe you know what you're doing...");
}
stubServer = stubServer.start();
}
private boolean hasRequest(Collection<Contract> contracts) {
for (Contract contract : contracts) {
if (contract.getRequest() != null) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging;
/**
* Factory of StubRunners. Basing on the options and passed collaborators downloads the
* stubs and returns a list of corresponding stub runners.
*/
public class StubRunnerFactory {
private final StubRunnerOptions stubRunnerOptions;
private final StubDownloader stubDownloader;
private final ContractVerifierMessaging<?, ?> contractVerifierMessaging;
public StubRunnerFactory(StubRunnerOptions stubRunnerOptions,
StubDownloader stubDownloader,
ContractVerifierMessaging<?, ?> contractVerifierMessaging) {
this.stubRunnerOptions = stubRunnerOptions;
this.stubDownloader = stubDownloader;
this.contractVerifierMessaging = contractVerifierMessaging;
}
public Collection<StubRunner> createStubsFromServiceConfiguration() {
Collection<StubRunner> result = new ArrayList<>();
for (StubConfiguration stubsConfiguration : stubRunnerOptions.getDependencies()) {
Map.Entry<StubConfiguration, File> entry = stubDownloader
.downloadAndUnpackStubJar(stubRunnerOptions, stubsConfiguration);
if (entry != null) {
result.add(createStubRunner(entry.getKey(), entry.getValue()));
}
}
return result;
}
private StubRunner createStubRunner(StubConfiguration stubsConfiguration,
File unzipedStubDir) {
if (unzipedStubDir == null) {
return null;
}
return createStubRunner(unzipedStubDir, stubsConfiguration, stubRunnerOptions);
}
private StubRunner createStubRunner(File unzippedStubsDir,
StubConfiguration stubsConfiguration, StubRunnerOptions stubRunnerOptions) {
return new StubRunner(stubRunnerOptions, unzippedStubsDir.getPath(),
stubsConfiguration, contractVerifierMessaging);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StubRunnerMain {
private static final Logger log = LoggerFactory.getLogger(StubRunnerMain.class);
@Option(name = "-sr", aliases = "--stubRepositoryRoot", usage = "Location of a Jar containing server where you keep your stubs (e.g. http://nexus.net/content/repositories/repository)", required = true)
private String stubRepositoryRoot;
@Option(name = "-ss", aliases = "--stubsSuffix", usage = "Suffix for the jar containing stubs (e.g. 'stubs' if the stub jar would have a 'stubs' classifier for stubs: foobar-stubs ). Defaults to 'stubs'")
private String stubsSuffix = "stubs";
@Option(name = "-minp", aliases = "--minPort", usage = "Minimal port value to be assigned to the WireMock instance. Defaults to 10000")
private Integer minPortValue = 10000;
@Option(name = "-maxp", aliases = "--maxPort", usage = "Maximum port value to be assigned to the WireMock instance. Defaults to 15000")
private Integer maxPortValue = 15000;
@Option(name = "-wo", aliases = "--workOffline", usage = "Switch to work offline. Defaults to 'false'")
private Boolean workOffline = Boolean.FALSE;
@Option(name = "-s", aliases = "--stubs", usage = "Comma separated list of Ivy representation of jars with stubs. Eg. groupid:artifactid1,groupid2:artifactid2:classifier")
private String stubs;
private final Arguments arguments;
private StubRunnerMain(String[] args) throws CmdLineException {
CmdLineParser parser = new CmdLineParser(this);
try {
parser.parseArgument(args);
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withMinMaxPort(minPortValue, maxPortValue)
.withStubRepositoryRoot(stubRepositoryRoot)
.withWorkOffline(workOffline)
.withStubsClassifier(stubsSuffix)
.withStubs(stubs)
.build();
this.arguments = new Arguments(stubRunnerOptions);
} catch (CmdLineException e) {
printErrorMessage(e, parser);
throw e;
}
}
private void printErrorMessage(CmdLineException e, CmdLineParser parser) {
System.err.println(e.getMessage());
System.err.println("java -jar stub-runner.jar [options...] ");
parser.printUsage(System.err);
System.err.println();
System.err.println("Example: java -jar stub-runner.jar ${parser.printExample(ALL)}");
}
static void main(String[] args) throws CmdLineException {
new StubRunnerMain(args).execute();
}
private void execute() {
try {
log.debug("Launching StubRunner with args: $arguments");
// TODO: Pass StubsToRun either from String or File
BatchStubRunner stubRunner = new BatchStubRunnerFactory(arguments.getStubRunnerOptions()).buildBatchStubRunner();
RunningStubs runningCollaborators = stubRunner.runStubs();
log.info(runningCollaborators.toString());
} catch (Exception e) {
log.error("An exception occurred while trying to execute the stubs", e);
throw e;
}
}
}

View File

@@ -14,22 +14,20 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
package org.springframework.cloud.contract.stubrunner;
import groovy.transform.PackageScope
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging;
/**
* @author Marcin Grzejszczak
*/
@PackageScope
class StubRunnerMessagingTrigger {
private final ContractVerifierMessaging contractVerifierMessaging
private final ContractVerifierMessaging<?,?> contractVerifierMessaging;
StubRunnerMessagingTrigger(ContractVerifierMessaging contractVerifierMessaging) {
this.contractVerifierMessaging = contractVerifierMessaging
StubRunnerMessagingTrigger(ContractVerifierMessaging<?,?> contractVerifierMessaging) {
this.contractVerifierMessaging = contractVerifierMessaging;
}
def trigger
// def trigger
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.util.Collection;
import java.util.Map;
/**
* Technical options related to running StubRunner
*
* Use {@class StubRunnerOptionsBuilder} to build this object.
*
* @see StubRunnerOptionsBuilder
*/
public class StubRunnerOptions {
/**
* min port value of the WireMock instance for the given collaborator
*/
final Integer minPortValue;
/**
* max port value of the WireMock instance for the given collaborator
*/
final Integer maxPortValue;
/**
* root URL from where the JAR with stub mappings will be downloaded
*/
final String stubRepositoryRoot;
/**
* avoids local repository in dependency resolution
*/
final boolean workOffline;
/**
* stub definition classifier
*/
final String stubsClassifier;
final Collection<StubConfiguration> dependencies;
/**
* colon separated list of ids to the desired port
*/
final Map<StubConfiguration, Integer> stubIdsToPortMapping;
public StubRunnerOptions(Integer minPortValue, Integer maxPortValue, String stubRepositoryRoot,
boolean workOffline, String stubsClassifier, Collection<StubConfiguration> dependencies, Map<StubConfiguration, Integer> stubIdsToPortMapping) {
this.minPortValue = minPortValue;
this.maxPortValue = maxPortValue;
this.stubRepositoryRoot = stubRepositoryRoot;
this.workOffline = workOffline;
this.stubsClassifier = stubsClassifier;
this.dependencies = dependencies;
this.stubIdsToPortMapping = stubIdsToPortMapping;
}
public Integer port(StubConfiguration stubConfiguration) {
if (stubIdsToPortMapping!=null) {
return stubIdsToPortMapping.get(stubConfiguration);
} else {
return null;
}
}
public Integer getMinPortValue() {
return minPortValue;
}
public Integer getMaxPortValue() {
return maxPortValue;
}
public String getStubRepositoryRoot() {
return stubRepositoryRoot;
}
public boolean isWorkOffline() {
return workOffline;
}
public String getStubsClassifier() {
return stubsClassifier;
}
public Collection<StubConfiguration> getDependencies() {
return dependencies;
}
public Map<StubConfiguration, Integer> getStubIdsToPortMapping() {
return stubIdsToPortMapping;
}
@Override
public String toString() {
return "StubRunnerOptions [minPortValue=" + minPortValue + ", maxPortValue="
+ maxPortValue + ", stubRepositoryRoot=" + stubRepositoryRoot
+ ", workOffline=" + workOffline + ", stubsClassifier=" + stubsClassifier
+ ", dependencies=" + dependencies + ", stubIdsToPortMapping="
+ stubIdsToPortMapping + "]";
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.cloud.contract.stubrunner.util.StubsParser;
public class StubRunnerOptionsBuilder {
private static final String DELIMITER = ":";
private LinkedList<String> stubs = new LinkedList<>();
private Map<StubConfiguration, Integer> stubIdsToPortMapping = new LinkedHashMap<>();
private Integer minPortValue = 10000;
private Integer maxPortValue = 15000;
private String stubRepositoryRoot;
private boolean workOffline = false;
private String stubsClassifier = "stubs";
public StubRunnerOptionsBuilder() {
}
public StubRunnerOptionsBuilder(StubRunnerOptions options) {
withOptions(options);
}
public StubRunnerOptionsBuilder withStubs(String stubs) {
addStub(stubsToList(stubs));
return this;
}
public StubRunnerOptionsBuilder withStubs(List<String> stubs) {
for (String stub : stubs) {
withStubs(stub);
}
return this;
}
public StubRunnerOptionsBuilder withMinMaxPort(Integer minPortValue,
Integer maxPortValue) {
this.minPortValue = minPortValue;
this.maxPortValue = maxPortValue;
return this;
}
public StubRunnerOptionsBuilder withMinPort(int minPortValue) {
this.minPortValue = minPortValue;
return this;
}
public StubRunnerOptionsBuilder withMaxPort(int maxPortValue) {
this.maxPortValue = maxPortValue;
return this;
}
public StubRunnerOptionsBuilder withStubRepositoryRoot(String stubRepositoryRoot) {
this.stubRepositoryRoot = stubRepositoryRoot;
return this;
}
public StubRunnerOptionsBuilder withWorkOffline(boolean workOffline) {
this.workOffline = workOffline;
return this;
}
public StubRunnerOptionsBuilder withStubsClassifier(String stubsClassifier) {
this.stubsClassifier = stubsClassifier;
return this;
}
public StubRunnerOptionsBuilder withPort(Integer port) {
String lastStub = stubs.peekLast();
addPort(lastStub + DELIMITER + port);
return this;
}
public StubRunnerOptionsBuilder withOptions(StubRunnerOptions options) {
this.minPortValue = options.minPortValue;
this.maxPortValue = options.maxPortValue;
this.stubRepositoryRoot = options.stubRepositoryRoot;
this.workOffline = options.workOffline;
this.stubsClassifier = options.stubsClassifier;
return this;
}
public StubRunnerOptions build() {
return new StubRunnerOptions(minPortValue, maxPortValue, stubRepositoryRoot,
workOffline, stubsClassifier, buildDependencies(), stubIdsToPortMapping);
}
private Collection<StubConfiguration> buildDependencies() {
return StubsParser.fromString(stubs, stubsClassifier);
}
private static List<String> stubsToList(String stubIdsToPortMapping) {
return Arrays.asList(stubIdsToPortMapping.split(","));
}
private void addStub(List<String> notations) {
for (String notation : notations) {
addStub(notation);
}
}
private void addStub(String notation) {
if (StubsParser.hasPort(notation)) {
addPort(notation);
stubs.add(StubsParser.ivyFromStringWithPort(notation));
}
else {
stubs.add(notation);
}
}
private void addPort(String notation) {
putStubIdsToPortMapping(StubsParser.fromStringWithPort(notation));
}
private void putStubIdsToPortMapping(
Map<StubConfiguration, Integer> stubIdsToPortMapping) {
this.stubIdsToPortMapping.putAll(stubIdsToPortMapping);
}
}

View File

@@ -14,12 +14,14 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
package org.springframework.cloud.contract.stubrunner;
interface StubRunning extends Closeable, StubFinder {
import java.io.Closeable;
public interface StubRunning extends Closeable, StubFinder {
/**
* Runs the stubs and returns the {@link RunningStubs}
*/
RunningStubs runStubs()
RunningStubs runStubs();
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.wiremock.WireMockSpring;
import org.springframework.util.ClassUtils;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
public class StubServer {
private static final Logger log = LoggerFactory.getLogger(StubServer.class);
private WireMockServer wireMockServer;
final StubConfiguration stubConfiguration;
final Collection<WiremockMappingDescriptor> mappings;
final Collection<Contract> contracts;
public StubServer(int port, StubConfiguration stubConfiguration, Collection<WiremockMappingDescriptor> mappings,
Collection<Contract> contracts) {
this.stubConfiguration = stubConfiguration;
this.mappings = mappings;
this.wireMockServer = new WireMockServer(config().port(port));
this.contracts = contracts;
}
private WireMockConfiguration config() {
if (ClassUtils.isPresent("org.springframework.cloud.contract.wiremock.WireMockSpring", null)) {
return WireMockSpring.options();
}
return new WireMockConfiguration();
}
public StubServer start() {
wireMockServer.start();
log.info("Started stub server for project ${stubConfiguration.toColonSeparatedDependencyNotation()} on port ${wireMockServer.port()}");
registerStubMappings();
return this;
}
public void stop() {
wireMockServer.stop();
}
public int getPort() {
if (wireMockServer.isRunning()) {
return wireMockServer.port();
}
log.debug("The HTTP Server stub is not running... That means that the " +
"artifact is running a messaging module. Returning back -1 value of the port.");
return -1;
}
public URL getStubUrl() {
try {
return new URL("http://localhost:" + getPort());
}
catch (MalformedURLException e) {
throw new IllegalStateException("Cannot parse URL", e);
}
}
public WireMockServer getWireMockServer() {
return wireMockServer;
}
public StubConfiguration getStubConfiguration() {
return stubConfiguration;
}
public Collection<WiremockMappingDescriptor> getMappings() {
return mappings;
}
public Collection<Contract> getContracts() {
return contracts;
}
private void registerStubMappings() {
WireMock wireMock = new WireMock("localhost", wireMockServer.port());
registerDefaultHealthChecks(wireMock);
registerStubs(mappings, wireMock);
}
private void registerDefaultHealthChecks(WireMock wireMock) {
registerHealthCheck(wireMock, "/ping");
registerHealthCheck(wireMock, "/health");
}
private void registerStubs(Collection<WiremockMappingDescriptor> sortedMappings, WireMock wireMock) {
for (WiremockMappingDescriptor mappingDescriptor : sortedMappings) {
try {
wireMock.register(mappingDescriptor.getMapping());
log.debug("Registered stub mappings from $mappingDescriptor.descriptor");
} catch (Exception e) {
log.warn("Failed to register the stub mapping ["+ mappingDescriptor + "]", e);
}
}
}
private void registerHealthCheck(WireMock wireMock, String url) {
registerHealthCheck(wireMock, url, "OK");
}
private void registerHealthCheck(WireMock wireMock, String url, String body) {
wireMock.register(WireMock.get(WireMock.urlEqualTo(url)).willReturn(WireMock.aResponse().withBody(body).withStatus(200)));
}
}

View File

@@ -14,9 +14,12 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner
package org.springframework.cloud.contract.stubrunner;
interface StubTrigger {
import java.util.Collection;
import java.util.Map;
public interface StubTrigger {
/**
* Triggers an event by a given label for a given {@code groupid:artifactid} notation. You can use only {@code artifactId} too.
@@ -25,7 +28,7 @@ interface StubTrigger {
*
* @return true - if managed to run a trigger
*/
boolean trigger(String ivyNotation, String labelName)
boolean trigger(String ivyNotation, String labelName);
/**
* Triggers an event by a given label.
@@ -34,7 +37,7 @@ interface StubTrigger {
*
* @return true - if managed to run a trigger
*/
boolean trigger(String labelName)
boolean trigger(String labelName);
/**
* Triggers all possible events.
@@ -43,12 +46,12 @@ interface StubTrigger {
*
* @return true - if managed to run a trigger
*/
boolean trigger()
boolean trigger();
/**
* Returns a mapping of ivy notation of a dependency to all the labels it has.
*
* Feature related to messaging.
*/
Map<String, Collection<String>> labels()
Map<String, Collection<String>> labels();
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import org.springframework.util.StreamUtils;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
/**
* Represents a single JSON file that was found in the folder with potential WireMock
* stubs
*/
public class WiremockMappingDescriptor {
final File descriptor;
public WiremockMappingDescriptor(File mappingDescriptor) {
this.descriptor = mappingDescriptor;
}
public StubMapping getMapping() {
try {
return StubMapping.buildFrom(StreamUtils.copyToString(
new FileInputStream(descriptor), Charset.forName("UTF-8")));
}
catch (IOException e) {
throw new IllegalStateException("Cannot read file", e);
}
}
@Override
public String toString() {
return "WiremockMappingDescriptor [descriptor=" + descriptor + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((descriptor == null) ? 0 : descriptor.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
WiremockMappingDescriptor other = (WiremockMappingDescriptor) obj;
if (descriptor == null) {
if (other.descriptor != null)
return false;
}
else if (!descriptor.equals(other.descriptor))
return false;
return true;
}
}

View File

@@ -14,21 +14,23 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.util
package org.springframework.cloud.contract.stubrunner.util;
/**
* Utils ported from Apache Commons
*
* @author Marcin Grzejszczak
*/
class StringUtils {
public static String EMPTY = ""
private static int INDEX_NOT_FOUND = -1
public class StringUtils {
public static String EMPTY = "";
private static int INDEX_NOT_FOUND = -1;
// Empty checks
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
/**
* <p>Checks if a String is empty ("") or null.</p>
* <p>
* Checks if a String is empty ("") or null.
* </p>
*
* <pre>
* StringUtils.isEmpty(null) = true
@@ -38,9 +40,10 @@ class StringUtils {
* StringUtils.isEmpty(" bob ") = false
* </pre>
*
* <p>NOTE: This method changed in Lang version 2.0.
* It no longer trims the String.
* That functionality is available in isBlank().</p>
* <p>
* NOTE: This method changed in Lang version 2.0. It no longer trims the String. That
* functionality is available in isBlank().
* </p>
*
* @param str the String to check, may be null
* @return <code>true</code> if the String is empty or null
@@ -49,23 +52,38 @@ class StringUtils {
return str == null || str.length() == 0;
}
static boolean isNotEmpty(String string) {
return string != null && !string.empty
public static boolean isNotEmpty(String string) {
return string != null && !string.isEmpty();
}
static boolean hasText(String string) {
return isNotEmpty(string) && !string.allWhitespace
public static boolean hasText(String string) {
if (!isNotEmpty(string)) {
return false;
}
int strLen = string.length();
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(string.charAt(i))) {
return true;
}
}
return false;
}
/**
* <p>Gets the substring before the last occurrence of a separator.
* The separator is not returned.</p>
* <p>
* Gets the substring before the last occurrence of a separator. The separator is not
* returned.
* </p>
*
* <p>A <code>null</code> string input will return <code>null</code>.
* An empty ("") string input will return the empty string.
* An empty or <code>null</code> separator will return the input string.</p>
* <p>
* A <code>null</code> string input will return <code>null</code>. An empty ("")
* string input will return the empty string. An empty or <code>null</code> separator
* will return the input string.
* </p>
*
* <p>If nothing is found, the string input is returned.</p>
* <p>
* If nothing is found, the string input is returned.
* </p>
*
* <pre>
* StringUtils.substringBeforeLast(null, *) = null
@@ -81,7 +99,7 @@ class StringUtils {
* @param str the String to get a substring from, may be null
* @param separator the String to search for, may be null
* @return the substring before the last occurrence of the separator,
* <code>null</code> if null String input
* <code>null</code> if null String input
* @since 2.0
*/
public static String substringBeforeLast(String str, String separator) {
@@ -96,15 +114,20 @@ class StringUtils {
}
/**
* <p>Gets the substring after the last occurrence of a separator.
* The separator is not returned.</p>
* <p>
* Gets the substring after the last occurrence of a separator. The separator is not
* returned.
* </p>
*
* <p>A <code>null</code> string input will return <code>null</code>.
* An empty ("") string input will return the empty string.
* An empty or <code>null</code> separator will return the empty string if
* the input string is not <code>null</code>.</p>
* <p>
* A <code>null</code> string input will return <code>null</code>. An empty ("")
* string input will return the empty string. An empty or <code>null</code> separator
* will return the empty string if the input string is not <code>null</code>.
* </p>
*
* <p>If nothing is found, the empty string is returned.</p>
* <p>
* If nothing is found, the empty string is returned.
* </p>
*
* <pre>
* StringUtils.substringAfterLast(null, *) = null
@@ -120,8 +143,8 @@ class StringUtils {
*
* @param str the String to get a substring from, may be null
* @param separator the String to search for, may be null
* @return the substring after the last occurrence of the separator,
* <code>null</code> if null String input
* @return the substring after the last occurrence of the separator, <code>null</code>
* if null String input
* @since 2.0
*/
public static String substringAfterLast(String str, String separator) {

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.util;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.cloud.contract.stubrunner.StubConfiguration;
/**
* Utility to parse string into a list of configuration of stubs
*/
public class StubsParser {
/**
* The string is expected to be a map with entry called "stubs"
* that contains a list of Strings in the format
*
* <ul>
* <li>groupid:artifactid:classifier</li>
* <li>groupid:artifactid</li>
* </ul>
*
* In the latter case the provided default stub classifier will be passed.
*
* Example:
*
* "a:b,c:d:e"
*/
public static Set<StubConfiguration> fromString(String list, String defaultClassifier) {
List<String> splitList = Arrays.asList(list.split(","));
return fromString(splitList, defaultClassifier);
}
public static Set<StubConfiguration> fromString(Collection<String> collection, String defaultClassifier) {
Set<StubConfiguration> stubs = new LinkedHashSet<>();
for (String config : collection) {
if (StringUtils.hasText(config)) {
stubs.add(StubSpecification.parse(config, defaultClassifier).stub);
}
}
return stubs;
}
public static Map<StubConfiguration, Integer> fromStringWithPort(String notation) {
StubSpecification stub = StubSpecification.parse(notation, StubConfiguration.DEFAULT_CLASSIFIER);
if (!stub.hasPort()) {
return Collections.emptyMap();
}
return Collections.singletonMap(stub.stub, stub.port);
}
public static String ivyFromStringWithPort(String notation) {
StubSpecification stub = StubSpecification.parse(notation, StubConfiguration.DEFAULT_CLASSIFIER);
if (!stub.hasPort()) {
return "";
}
return stub.stub.toColonSeparatedDependencyNotation();
}
public static boolean hasPort(String id) {
String[] splitEntry = id.split(":");
try {
Integer.valueOf(splitEntry[splitEntry.length-1]);
return true;
} catch (NumberFormatException e) {
return false;
}
}
private static class StubSpecification {
private StubConfiguration stub;
private Integer port;
public StubSpecification(StubConfiguration stub, Integer port) {
this.stub = stub;
this.port = port;
}
public boolean hasPort() {
return port!=null;
}
private static StubSpecification parse(String id, String defaultClassifier) {
String[] splitEntry = id.split(":");
Integer port = null;
try {
port = Integer.valueOf(splitEntry[splitEntry.length-1]);
id = id.substring(0, id.lastIndexOf(":"));
} catch (NumberFormatException e) {
}
return new StubSpecification(new StubConfiguration(id, defaultClassifier), port);
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.springframework.util.StreamUtils;
/**
* Based on
* <a href="https://github.com/timyates/groovy-common-extensions">https://github.com/
* timyates/groovy-common-extensions</a>.
*
* Category for {@link File} that adds a method that allows you to unzip a given file to a
* specified location
*
*/
public class ZipCategory {
/**
* Unzips this file. If the <tt>destination</tt> directory is not provided, it will
* fall back to this file's parent directory.
*
* @param self
* @param destination (optional), the destination directory where this file's content
* will be unzipped to.
* @return a {@link Collection} of unzipped {@link File} objects.
*/
public static Collection<File> unzipTo(File self, File destination) {
checkUnzipDestination(destination);
// if destination directory is not given, we'll fall back to the parent directory
// of 'self'
if (destination == null)
destination = new File(self.getParent());
List<File> unzippedFiles = new ArrayList<>();
try {
ZipInputStream zipInput = new ZipInputStream(new FileInputStream(self));
for (ZipEntry entry = zipInput.getNextEntry(); entry != null; entry = zipInput
.getNextEntry()) {
if (!entry.isDirectory()) {
final File file = new File(destination, entry.getName());
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
try (FileOutputStream output = new FileOutputStream(file)) {
StreamUtils.copy(zipInput, output);
}
unzippedFiles.add(file);
}
else {
final File dir = new File(destination, entry.getName());
dir.mkdirs();
unzippedFiles.add(dir);
}
}
}
catch (IOException e) {
throw new IllegalStateException("Cannot unzip archive", e);
}
return unzippedFiles;
}
private static void checkUnzipDestination(File file) {
if (file != null && !file.isDirectory())
throw new IllegalArgumentException("'destination' has to be a directory.");
}
}

View File

@@ -45,6 +45,6 @@ class StubRepositorySpec extends Specification {
when:
new StubRepository(new File('src/test/resources/nonexistingrepo'))
then:
thrown(FileNotFoundException)
thrown(IllegalArgumentException)
}
}

View File

@@ -16,22 +16,24 @@
package org.springframework.cloud.contract.verifier.messaging.camel;
import org.apache.camel.CamelContext;
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessageBuilder;
import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMessaging;
import org.apache.camel.CamelContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Marcin Grzejszczak
*/
@Configuration
public class ContractVerifierCamelConfiguration {
@Bean ContractVerifierMessaging contractVerifierMessaging(CamelContext context,
ContractVerifierMessageBuilder builder) {
return new ContractVerifierCamelMessaging(context, builder);
@Bean ContractVerifierMessaging<?,?> contractVerifierMessaging(CamelContext context,
ContractVerifierMessageBuilder<?,?> builder) {
return new ContractVerifierCamelMessaging<>(context, builder);
}
@Bean ContractVerifierMessageBuilder contractVerifierMessageBuilder() {
return new ContractVerifierCamelMessageBuilder();
@Bean ContractVerifierMessageBuilder<?,?> contractVerifierMessageBuilder() {
return new ContractVerifierCamelMessageBuilder<>();
}
}

View File

@@ -21,23 +21,24 @@ import org.springframework.cloud.contract.verifier.messaging.ContractVerifierMes
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
/**
* @author Marcin Grzejszczak
*/
@Configuration
public class ContractVerifierIntegrationConfiguration {
public class ContractVerifierIntegrationConfiguration<T> {
@Bean
public ContractVerifierMessaging<?, ?> contractVerifierMessaging(
public ContractVerifierMessaging<T, Message<T>> contractVerifierMessaging(
ApplicationContext applicationContext,
ContractVerifierMessageBuilder<?,?> contractVerifierMessageBuilder) {
return new ContractVerifierIntegrationMessaging<>(applicationContext,
ContractVerifierMessageBuilder<T, Message<T>> contractVerifierMessageBuilder) {
return new ContractVerifierIntegrationMessaging<T>(applicationContext,
contractVerifierMessageBuilder);
}
@Bean
public ContractVerifierMessageBuilder<?, ?> contractVerifierMessageBuilder() {
public ContractVerifierMessageBuilder<T, Message<T>> contractVerifierMessageBuilder() {
return new ContractVerifierIntegrationMessageBuilder<>();
}
}

View File

@@ -43,17 +43,15 @@ public class ContractVerifierIntegrationMessaging<T> implements
ContractVerifierIntegrationMessaging.class);
private final ApplicationContext context;
private final ContractVerifierMessageBuilder builder;
private final ContractVerifierMessageBuilder<T, Message<T>> builder;
@Autowired
@SuppressWarnings("unchecked")
public ContractVerifierIntegrationMessaging(ApplicationContext context, ContractVerifierMessageBuilder contractVerifierMessageBuilder) {
public ContractVerifierIntegrationMessaging(ApplicationContext context, ContractVerifierMessageBuilder<T, Message<T>> contractVerifierMessageBuilder) {
this.context = context;
this.builder = contractVerifierMessageBuilder;
}
@Override
@SuppressWarnings("unchecked")
public void send(T payload, Map<String, Object> headers, String destination) {
send(builder.create(payload, headers), destination);
}
@@ -75,7 +73,7 @@ public class ContractVerifierIntegrationMessaging<T> implements
public ContractVerifierMessage<T, Message<T>> receiveMessage(String destination, long timeout, TimeUnit timeUnit) {
try {
PollableChannel messageChannel = context.getBean(destination, PollableChannel.class);
return builder.create(messageChannel.receive(timeUnit.toMillis(timeout)));
return builder.create((Message<T>) messageChannel.receive(timeUnit.toMillis(timeout)));
} catch (Exception e) {
log.error("Exception occurred while trying to read a message from " +
" a channel with name [" + destination + "]", e);
@@ -89,13 +87,11 @@ public class ContractVerifierIntegrationMessaging<T> implements
}
@Override
@SuppressWarnings("unchecked")
public ContractVerifierMessage<T, Message<T>> create(T t, Map<String, Object> headers) {
return builder.create(t, headers);
}
@Override
@SuppressWarnings("unchecked")
public ContractVerifierMessage<T, Message<T>> create(Message<T> message) {
return builder.create(message);
}