/*
* 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.node;
import com.couchbase.client.core.message.CouchbaseRequest;
import com.couchbase.client.core.message.analytics.AnalyticsRequest;
import com.couchbase.client.core.message.config.ConfigRequest;
import com.couchbase.client.core.message.dcp.DCPRequest;
import com.couchbase.client.core.message.kv.BinaryRequest;
import com.couchbase.client.core.message.query.QueryRequest;
import com.couchbase.client.core.message.search.SearchRequest;
import com.couchbase.client.core.message.view.ViewRequest;
import com.couchbase.client.core.service.BucketServiceMapping;
import com.couchbase.client.core.service.Service;
import com.couchbase.client.core.service.ServiceType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* The default implementation of a {@link ServiceRegistry}.
*/
public class DefaultServiceRegistry implements ServiceRegistry {
/**
* Contains services which work across buckets.
*/
private final Map<ServiceType, Service> globalServices;
/**
* Contains bucket-local services.
*/
private final Map<String, Map<ServiceType, Service>> localServices;
private volatile Service[] serviceCache;
/**
* Create a new {@link DefaultServiceRegistry} with custom containers.
*
* This constructor is intended to be used for unit tests only.
*
* @param globalServices the global service set.
* @param localServices the bucket-local services.
*/
DefaultServiceRegistry(final Map<ServiceType, Service> globalServices,
final Map<String, Map<ServiceType, Service>> localServices) {
this.globalServices = globalServices;
this.localServices = localServices;
this.serviceCache = new Service[] {};
}
/**
* Create a new {@link DefaultServiceRegistry}.
*/
public DefaultServiceRegistry() {
this(new ConcurrentHashMap<ServiceType, Service>(), new ConcurrentHashMap<String, Map<ServiceType, Service>>());
}
@Override
public Service addService(final Service service, final String bucket) {
if (service.mapping() == BucketServiceMapping.ONE_BY_ONE) {
if (!localServices.containsKey(bucket)) {
localServices.put(bucket, new ConcurrentHashMap<ServiceType, Service>());
}
if (!localServices.get(bucket).containsKey(service.type())) {
localServices.get(bucket).put(service.type(), service);
}
} else {
if (!globalServices.containsKey(service.type())) {
globalServices.put(service.type(), service);
}
}
recalculateServiceCache();
return service;
}
@Override
public Service removeService(final Service service, final String bucket) {
if (service.mapping() == BucketServiceMapping.ONE_BY_ONE) {
if (localServices.containsKey(bucket) && localServices.get(bucket).containsKey(service.type())) {
localServices.get(bucket).remove(service.type());
}
if (localServices.get(bucket).isEmpty()) {
localServices.remove(bucket);
}
} else {
if (globalServices.containsKey(service.type())) {
globalServices.remove(service.type());
}
}
recalculateServiceCache();
return service;
}
@Override
public Service locate(final CouchbaseRequest request) {
ServiceType type = serviceTypeFor(request);
if (type.mapping() == BucketServiceMapping.ONE_BY_ONE) {
Map<ServiceType, Service> services = localServices.get(request.bucket());
if (services == null) {
return null;
}
return services.get(type);
} else {
return globalServices.get(type);
}
}
@Override
public Service[] services() {
return serviceCache;
}
private void recalculateServiceCache() {
List<Service> services = new ArrayList<Service>();
for (Service service : globalServices.values()) {
services.add(service);
}
for (Map<ServiceType, Service> bucket : localServices.values()) {
for (Service service : bucket.values()) {
services.add(service);
}
}
serviceCache = services.toArray(new Service[services.size()]);
}
@Override
public Service serviceBy(final ServiceType type, final String bucket) {
if (type.mapping() == BucketServiceMapping.ONE_BY_ONE) {
if (localServices.get(bucket) == null) {
return null;
}
return localServices.get(bucket).get(type);
} else {
return globalServices.get(type);
}
}
/**
* Returns the mapping for a given {@link CouchbaseRequest}.
*
* @param request the request to check.
* @return the mapping for the request.
*/
private static ServiceType serviceTypeFor(final CouchbaseRequest request) {
if (request instanceof BinaryRequest) {
return ServiceType.BINARY;
} else if (request instanceof ConfigRequest) {
return ServiceType.CONFIG;
} else if (request instanceof ViewRequest) {
return ServiceType.VIEW;
} else if (request instanceof QueryRequest) {
return ServiceType.QUERY;
} else if (request instanceof DCPRequest) {
return ServiceType.DCP;
} else if (request instanceof SearchRequest) {
return ServiceType.SEARCH;
} else if (request instanceof AnalyticsRequest) {
return ServiceType.ANALYTICS;
} else {
throw new IllegalStateException("Unknown Request: " + request);
}
}
@Override
public String toString() {
return "DefaultServiceRegistry{"
+ "globalServices=" + globalServices
+ ", localServices=" + localServices
+ ", serviceCache=" + serviceCache
+ '}';
}
}