/*
* Copyright (c) 2016 Couchbase, Inc.
*
* 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 com.couchbase.client.core.config.refresher;
import com.couchbase.client.core.ClusterFacade;
import com.couchbase.client.core.config.BucketConfig;
import com.couchbase.client.core.config.ClusterConfig;
import com.couchbase.client.core.config.ConfigurationException;
import com.couchbase.client.core.env.CoreEnvironment;
import com.couchbase.client.core.logging.CouchbaseLogger;
import com.couchbase.client.core.logging.CouchbaseLoggerFactory;
import com.couchbase.client.core.message.config.BucketStreamingRequest;
import com.couchbase.client.core.message.config.BucketStreamingResponse;
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
/**
* Keeps the bucket config fresh through a HTTP streaming connection.
*
* @author Michael Nitschinger
* @since 1.0
*/
public class HttpRefresher extends AbstractRefresher {
/**
* The logger used.
*/
private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(HttpRefresher.class);
private static final String TERSE_PATH = "/pools/default/bs/";
private static final String VERBOSE_PATH = "/pools/default/bucketsStreaming/";
public HttpRefresher(final CoreEnvironment env, final ClusterFacade cluster) {
super(env, cluster);
}
@Override
public Observable<Boolean> registerBucket(final String name, final String password) {
return registerBucket(name, name, password);
}
@Override
public Observable<Boolean> registerBucket(final String name, final String username, final String password) {
Observable<BucketStreamingResponse> response = super.registerBucket(name, username, password).flatMap(new Func1<Boolean, Observable<BucketStreamingResponse>>() {
@Override
public Observable<BucketStreamingResponse> call(Boolean aBoolean) {
return cluster()
.<BucketStreamingResponse>send(new BucketStreamingRequest(TERSE_PATH, name, username, password))
.doOnNext(new Action1<BucketStreamingResponse>() {
@Override
public void call(BucketStreamingResponse response) {
if (!response.status().isSuccess()) {
throw new ConfigurationException("Could not load terse config.");
}
}
});
}
}).onErrorResumeNext(new Func1<Throwable, Observable<BucketStreamingResponse>>() {
@Override
public Observable<BucketStreamingResponse> call(Throwable throwable) {
return cluster()
.<BucketStreamingResponse>send(new BucketStreamingRequest(VERBOSE_PATH, name, username, password))
.doOnNext(new Action1<BucketStreamingResponse>() {
@Override
public void call(BucketStreamingResponse response) {
if (!response.status().isSuccess()) {
throw new ConfigurationException("Could not load terse config.");
}
}
});
}
});
repeatConfigUntilUnsubscribed(name, response);
return response.map(new Func1<BucketStreamingResponse, Boolean>() {
@Override
public Boolean call(BucketStreamingResponse response) {
return response.status().isSuccess();
}
});
}
/**
* Helper method to push configs until unsubscribed, even when a stream closes.
*
* @param name the name of the bucket.
* @param response the response source observable to resubscribe if needed.
*/
private void repeatConfigUntilUnsubscribed(final String name, Observable<BucketStreamingResponse> response) {
response.flatMap(new Func1<BucketStreamingResponse, Observable<String>>() {
@Override
public Observable<String> call(final BucketStreamingResponse response) {
LOGGER.debug("Config stream started for {} on {}.", name, response.host());
return response
.configs()
.map(new Func1<String, String>() {
@Override
public String call(String s) {
return s.replace("$HOST", response.host());
}
})
.doOnCompleted(new Action0() {
@Override
public void call() {
LOGGER.debug("Config stream ended for {} on {}.", name, response.host());
}
});
}
})
.repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Void> observable) {
return observable.flatMap(new Func1<Void, Observable<?>>() {
@Override
public Observable<?> call(Void aVoid) {
if (HttpRefresher.this.registrations().containsKey(name)) {
LOGGER.debug("Resubscribing config stream for bucket {}, still registered.", name);
return Observable.just(true);
} else {
LOGGER.debug("Not resubscribing config stream for bucket {}, not registered.", name);
return Observable.empty();
}
}
});
}
}).subscribe(new Action1<String>() {
@Override
public void call(String rawConfig) {
pushConfig(rawConfig);
}
});
}
@Override
public Observable<Boolean> shutdown() {
return Observable.just(true);
}
@Override
public void markTainted(BucketConfig config) {
}
@Override
public void markUntainted(BucketConfig config) {
}
@Override
public void refresh(ClusterConfig config) {
}
}