package org.apereo.cas.services;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.view.DefaultView;
import com.couchbase.client.java.view.View;
import com.couchbase.client.java.view.ViewQuery;
import com.couchbase.client.java.view.ViewResult;
import com.couchbase.client.java.view.ViewRow;
import com.google.common.base.Throwables;
import org.apereo.cas.couchbase.core.CouchbaseClientFactory;
import org.apereo.cas.util.serialization.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PreDestroy;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* This is {@link CouchbaseServiceRegistryDao}.
* A Service Registry storage backend which uses the memcached protocol.
* This may seem like a weird idea until you realize that CouchBase is a
* multi host NoSQL database with a memcached interface to persistent
* storage which also is quite usable as a replicated ticket storage
* engine for multiple front end CAS servers.
*
* @author Fredrik Jönsson "fjo@kth.se"
* @author Misagh Moayyed
* @since 4.2.0
*/
public class CouchbaseServiceRegistryDao implements ServiceRegistryDao {
private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseServiceRegistryDao.class);
private static final View ALL_SERVICES_VIEW = DefaultView.create(
"all_services",
"function(d,m) {if (!isNaN(m.id)) {emit(m.id);}}");
private static final List<View> ALL_VIEWS = Arrays.asList(new View[]{ALL_SERVICES_VIEW});
private static final String UTIL_DOCUMENT = "utils";
private final CouchbaseClientFactory couchbase;
private final StringSerializer<RegisteredService> registeredServiceJsonSerializer;
/**
* Default constructor.
*
* @param couchbase couchbase instance
* @param serviceJsonSerializer the JSON serializer to use.
* @param isQueryEnabled the is query enabled
*/
public CouchbaseServiceRegistryDao(final CouchbaseClientFactory couchbase, final StringSerializer<RegisteredService> serviceJsonSerializer,
final boolean isQueryEnabled) {
this.couchbase = couchbase;
this.registeredServiceJsonSerializer = serviceJsonSerializer;
System.setProperty("com.couchbase.queryEnabled", Boolean.toString(isQueryEnabled));
this.couchbase.ensureIndexes(UTIL_DOCUMENT, ALL_VIEWS);
this.couchbase.initialize();
}
@Override
public RegisteredService save(final RegisteredService service) {
LOGGER.debug("Saving service [{}]", service);
if (service.getId() == AbstractRegisteredService.INITIAL_IDENTIFIER_VALUE) {
((AbstractRegisteredService) service).setId(service.hashCode());
}
final StringWriter stringWriter = new StringWriter();
this.registeredServiceJsonSerializer.to(stringWriter, service);
this.couchbase.bucket().upsert(
RawJsonDocument.create(
String.valueOf(service.getId()),
0, stringWriter.toString()));
return service;
}
@Override
public boolean delete(final RegisteredService service) {
LOGGER.debug("Deleting service [{}]", service);
this.couchbase.bucket().remove(String.valueOf(service.getId()));
return true;
}
@Override
public List<RegisteredService> load() {
try {
LOGGER.debug("Loading services");
final ViewResult allKeys = executeViewQueryForAllServices();
final List<RegisteredService> services = new LinkedList<>();
for (final ViewRow row : allKeys) {
final RawJsonDocument document = row.document(RawJsonDocument.class);
if (document != null) {
final String json = document.content();
LOGGER.debug("Found service: [{}]", json);
final StringReader stringReader = new StringReader(json);
services.add(this.registeredServiceJsonSerializer.from(stringReader));
}
}
return services;
} catch (final RuntimeException e) {
LOGGER.error(e.getMessage(), e);
return new LinkedList<>();
}
}
private ViewResult executeViewQueryForAllServices() {
return this.couchbase.bucket().query(ViewQuery.from(UTIL_DOCUMENT, ALL_SERVICES_VIEW.name()));
}
@Override
public RegisteredService findServiceById(final long id) {
try {
LOGGER.debug("Lookup for service [{}]", id);
final RawJsonDocument document = this.couchbase.bucket().get(String.valueOf(id), RawJsonDocument.class);
if (document != null) {
final String json = document.content();
final StringReader stringReader = new StringReader(json);
return this.registeredServiceJsonSerializer.from(stringReader);
}
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return null;
}
@Override
public RegisteredService findServiceById(final String id) {
return load().stream().filter(r -> r.matches(id)).findFirst().orElse(null);
}
/**
* Stops the couchbase client and cancels the initialization task if uncompleted.
*/
@PreDestroy
public void destroy() {
try {
this.couchbase.shutdown();
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public long size() {
return executeViewQueryForAllServices().totalRows();
}
}