Skip to content

Commit 0f2be58

Browse files
jonekdahlJeff Bornemann
authored andcommitted
Add https sync support (fix #149)
Add configuration tests for serverScheme Document serverScheme field (#149)
1 parent c0a0b01 commit 0f2be58

File tree

11 files changed

+136
-8
lines changed

11 files changed

+136
-8
lines changed

docs/GeneralLayout.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
There are two primary components to Grabbit: a client and a server that run in the two CQ instances that you want to copy to and from (respectively).
44

55
A recommended systems layout style is to have all content from a production publisher copied down to a staging "data warehouse" (DW) server to which all lower environments (beta, continuous integration, developer workstations, etc.) will connect. This way minimal load is placed on Production, and additional DW machines can be added to scale out if needed, each of which can grab from the "main" DW.
6-
The client sends an HTTP GET request with a content path and "last grab time" to the server and receives a protobuf stream of all the content below it that has changed. The client's BasicAuth credentials are used to create the JCR Session, so the client can never see content they don't have explicit access to. There are a number of ways to tune how the client works, including specifying multiple focused paths, parallel or serial execution, JCR Session batch size (the number of nodes to cache before flushing to disk), etc.
6+
The client sends an HTTP(S) GET request with a content path and "last grab time" to the server and receives a protobuf stream of all the content below it that has changed. The client's BasicAuth credentials are used to create the JCR Session, so the client can never see content they don't have explicit access to. There are a number of ways to tune how the client works, including specifying multiple focused paths, parallel or serial execution, JCR Session batch size (the number of nodes to cache before flushing to disk), etc.
77

docs/Running.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ The corresponding `YAML` configuration for the JSON above will look something li
130130

131131
===== Optional fields
132132

133+
* __serverScheme__: string. The protocol the client should use when connecting to the server. Supported options are `http` and `https`. Defaults to `http`.
133134
* __deltaContent__: boolean, ```true``` syncs only 'delta' or changed content. Changed content is determined by comparing one of a number of date properties including jcr:lastModified, cq:lastModified, or jcr:created Date with the last successful Grabbit sync date. Nodes without any of previously mentioned date properties will always be synced even with deltaContent on, and if a node's data is changed without updating a date property (ie, from CRX/DE), the change will not be detected. Most common throughput bottlenecks are usually handled by delta sync for cases such as large DAM trees; but if your case warrants a more fine tuned use of delta sync, you may consider adding mix:lastModified to nodes not usually considered for exclusion, such as extremely large unstructured trees. The deltaContent flag __only__ applies to changes made on the server - changes to the client environment will not be detected (and won't be overwritten if changes were made on the client's path but not on the server).
134135
* __batchSize__: integer. Used to specify the number of nodes in one batch, Defaults to 100.
135136
* __deleteBeforeWrite__: boolean. Before the client retrieves content, should content under each path be cleared? When used in combination with excludePaths, nodes indicated by excludePaths will not be deleted

sample-config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"serverUsername" : "<username>",
33
"serverPassword" : "<password>",
4+
"serverScheme" : "http",
45
"serverHost" : "some.other.server",
56
"serverPort" : "4502",
67
"batchSize" : 150,

sample_config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Information for connecting to the source content
44
serverUsername : '<username>'
55
serverPassword : '<password>'
6+
serverScheme : http
67
serverHost : some.other.server
78
serverPort : 4502
89

src/main/groovy/com/twcable/grabbit/GrabbitConfiguration.groovy

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class GrabbitConfiguration {
3535

3636
final String serverUsername
3737
final String serverPassword
38+
final String serverScheme
3839
final String serverHost
3940
final String serverPort
4041
final boolean deltaContent
@@ -45,12 +46,13 @@ class GrabbitConfiguration {
4546
final static int DEFAULT_BATCH_SIZE = 100
4647

4748

48-
private GrabbitConfiguration(@Nonnull String user, @Nonnull String pass, @Nonnull String host,
49-
@Nonnull String port, boolean deltaContent,
49+
private GrabbitConfiguration(@Nonnull String user, @Nonnull String pass, @Nonnull String scheme,
50+
@Nonnull String host, @Nonnull String port, boolean deltaContent,
5051
@Nonnull Collection<PathConfiguration> pathConfigs) {
5152
// all input is being verified by the "create" factory method
5253
this.serverUsername = user
5354
this.serverPassword = pass
55+
this.serverScheme = scheme
5456
this.serverHost = host
5557
this.serverPort = port
5658
this.deltaContent = deltaContent
@@ -84,6 +86,7 @@ class GrabbitConfiguration {
8486

8587
def serverUsername = nonEmpty(configMap, 'serverUsername', errorBuilder)
8688
def serverPassword = nonEmpty(configMap, 'serverPassword', errorBuilder)
89+
def serverScheme = schemeVal(configMap, 'serverScheme', errorBuilder)
8790
def serverHost = nonEmpty(configMap, 'serverHost', errorBuilder)
8891
def serverPort = nonEmpty(configMap, 'serverPort', errorBuilder)
8992
def deltaContent = boolVal(configMap, 'deltaContent')
@@ -110,6 +113,7 @@ class GrabbitConfiguration {
110113
return new GrabbitConfiguration(
111114
serverUsername,
112115
serverPassword,
116+
serverScheme,
113117
serverHost,
114118
serverPort,
115119
deltaContent,
@@ -151,6 +155,25 @@ class GrabbitConfiguration {
151155
}
152156

153157

158+
private static String schemeVal(Map<String, String> configMap, String key,
159+
ConfigurationException.Builder errorBuilder) {
160+
if (configMap.containsKey(key)) {
161+
def val = configMap.get(key).toLowerCase()
162+
if (val == "http" || val == "https") {
163+
return val
164+
}
165+
else {
166+
errorBuilder.add(key, 'must be either http or https')
167+
return null
168+
}
169+
}
170+
else {
171+
log.debug "Input doesn't contain ${key} for a URL scheme value. Will default to http"
172+
return "http"
173+
}
174+
}
175+
176+
154177
private static boolean boolVal(Map<String, String> configMap, String key) {
155178
if (!configMap.containsKey(key)) {
156179
log.debug "Input doesn't contain ${key} for a boolean value. Will default to false"

src/main/groovy/com/twcable/grabbit/client/batch/ClientBatchJob.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ClientBatchJob {
4040
public static final String PATH = "path"
4141
public static final String EXCLUDE_PATHS = "excludePaths"
4242
public static final String WORKFLOW_CONFIGS = "workflowConfigIds"
43+
public static final String SCHEME = "scheme"
4344
public static final String HOST = "host"
4445
public static final String PORT = "port"
4546
public static final String SERVER_USERNAME = "serverUsername"
@@ -84,6 +85,7 @@ class ClientBatchJob {
8485
@CompileStatic
8586
static class ServerBuilder {
8687
final ConfigurableApplicationContext configAppContext
88+
String scheme
8789
String host
8890
String port
8991

@@ -93,7 +95,8 @@ class ClientBatchJob {
9395
}
9496

9597

96-
CredentialsBuilder andServer(String host, String port) {
98+
CredentialsBuilder andServer(String scheme, String host, String port) {
99+
this.scheme = scheme
97100
this.host = host
98101
this.port = port
99102
return new CredentialsBuilder(this)
@@ -188,6 +191,7 @@ class ClientBatchJob {
188191
final jobParameters = [
189192
"timestamp" : System.currentTimeMillis() as String,
190193
(PATH) : pathConfiguration.path,
194+
(SCHEME) : serverBuilder.scheme,
191195
(HOST) : serverBuilder.host,
192196
(PORT) : serverBuilder.port,
193197
(CLIENT_USERNAME) : credentialsBuilder.clientUsername,

src/main/groovy/com/twcable/grabbit/client/batch/steps/http/CreateHttpConnectionTasklet.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,15 @@ class CreateHttpConnectionTasklet implements Tasklet {
9393
final String path = jobParameters.get(ClientBatchJob.PATH)
9494
final String excludePathParam = jobParameters.get(ClientBatchJob.EXCLUDE_PATHS)
9595
final excludePaths = (excludePathParam != null && !excludePathParam.isEmpty() ? excludePathParam.split(/\*/) : Collections.EMPTY_LIST) as Collection<String>
96+
final String scheme = jobParameters.get(ClientBatchJob.SCHEME)
9697
final String host = jobParameters.get(ClientBatchJob.HOST)
9798
final String port = jobParameters.get(ClientBatchJob.PORT)
9899
final String contentAfterDate = jobParameters.get(ClientBatchJob.CONTENT_AFTER_DATE) ?: ""
99100

100101
final String encodedContentAfterDate = URLEncoder.encode(contentAfterDate, 'utf-8')
101102
final String encodedPath = URLEncoder.encode(path, 'utf-8')
102103

103-
URIBuilder uriBuilder = new URIBuilder(scheme: "http", host: host, port: port as Integer, path: "/grabbit/content")
104+
URIBuilder uriBuilder = new URIBuilder(scheme: scheme, host: host, port: port as Integer, path: "/grabbit/content")
104105
uriBuilder.addParameter("path", encodedPath)
105106
uriBuilder.addParameter("after", encodedContentAfterDate)
106107
for(String excludePath : excludePaths) {

src/main/groovy/com/twcable/grabbit/client/services/impl/DefaultClientService.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class DefaultClientService implements ClientService {
6060
for (PathConfiguration pathConfig : configuration.pathConfigurations) {
6161
try {
6262
final clientBatchJob = new ClientBatchJob.ServerBuilder(configurableApplicationContext)
63-
.andServer(configuration.serverHost, configuration.serverPort)
63+
.andServer(configuration.serverScheme, configuration.serverHost, configuration.serverPort)
6464
.andCredentials(clientUsername, configuration.serverUsername, configuration.serverPassword)
6565
.andClientJobExecutions(fetchAllClientJobExecutions())
6666
.withTransactionID(configuration.transactionID)

src/test/groovy/com/twcable/grabbit/client/GrabbitConfigurationSpec.groovy

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,89 @@ class GrabbitConfigurationSpec extends Specification {
163163

164164
}
165165

166+
def "Should return http server scheme by default"() {
167+
given:
168+
def input = """
169+
{
170+
"serverUsername" : "admin",
171+
"serverPassword" : "admin",
172+
"serverHost" : "localhost",
173+
"serverPort" : "4503",
174+
"deltaContent" : false,
175+
"pathConfigurations" : [
176+
{
177+
"path" : "/content/a/b"
178+
}
179+
]
180+
}
181+
"""
182+
def expectedOutput = "http"
183+
184+
when:
185+
def actualOutput = GrabbitConfiguration.create(input)
186+
187+
then:
188+
actualOutput instanceof GrabbitConfiguration
189+
actualOutput.serverScheme == expectedOutput
190+
191+
}
192+
193+
def "Should return https scheme if configured"() {
194+
given:
195+
def input = """
196+
{
197+
"serverUsername" : "admin",
198+
"serverPassword" : "admin",
199+
"serverScheme" : "https",
200+
"serverHost" : "localhost",
201+
"serverPort" : "4503",
202+
"deltaContent" : false,
203+
"pathConfigurations" : [
204+
{
205+
"path" : "/content/a/b"
206+
}
207+
]
208+
}
209+
"""
210+
def expectedOutput = "https"
211+
212+
when:
213+
def actualOutput = GrabbitConfiguration.create(input)
214+
215+
then:
216+
actualOutput instanceof GrabbitConfiguration
217+
actualOutput.serverScheme == expectedOutput
218+
219+
}
220+
221+
def "Should fail to process invalid scheme"() {
222+
given:
223+
def input = """
224+
{
225+
"serverUsername" : "admin",
226+
"serverPassword" : "admin",
227+
"serverScheme" : "invalid-scheme",
228+
"serverHost" : "localhost",
229+
"serverPort" : "4503",
230+
"deltaContent" : false,
231+
"pathConfigurations" : [
232+
{
233+
"path" : "/content/a/b"
234+
}
235+
]
236+
}
237+
"""
238+
def errors = [serverScheme: "must be either http or https"]
239+
240+
when:
241+
GrabbitConfiguration.create(input)
242+
243+
then:
244+
final GrabbitConfiguration.ConfigurationException exception = thrown()
245+
exception.errors == errors
246+
247+
}
248+
166249
@Unroll
167250
def "Should create configuration from json input"() {
168251
when:

src/test/groovy/com/twcable/grabbit/client/batch/ClientBatchJobSpec.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class ClientBatchJobSpec extends Specification {
4646
final appContext = Mock(ConfigurableApplicationContext)
4747
appContext.getBean(_ as String, JobOperator) >> Mock(JobOperator)
4848
final job = new ClientBatchJob.ServerBuilder(appContext)
49-
.andServer("host", "port")
49+
.andServer("scheme", "host", "port")
5050
.andCredentials("clientUser", "serverUser", "serverPass")
5151
.andClientJobExecutions(jobExecutions)
5252
.andConfiguration(new GrabbitConfiguration.PathConfiguration(path, [], [], deleteBeforeWrite, pathDeltaContent, 100))

0 commit comments

Comments
 (0)