package org.apereo.cas.services;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.support.events.service.CasRegisteredServiceDeletedEvent;
import org.apereo.cas.support.events.service.CasRegisteredServiceSavedEvent;
import org.apereo.inspektr.audit.annotation.Audit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Default implementation of the {@link ServicesManager} interface.
*
* @author Scott Battaglia
* @since 3.1
*/
public class DefaultServicesManager implements ServicesManager, Serializable {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultServicesManager.class);
private static final long serialVersionUID = -8581398063126547772L;
private final ServiceRegistryDao serviceRegistryDao;
@Autowired
private transient ApplicationEventPublisher eventPublisher;
private Map<Long, RegisteredService> services = new ConcurrentHashMap<>();
private Set<RegisteredService> orderedServices = new ConcurrentSkipListSet<>();
/**
* Instantiates a new default services manager impl.
*
* @param serviceRegistryDao the service registry dao
*/
public DefaultServicesManager(final ServiceRegistryDao serviceRegistryDao) {
this.serviceRegistryDao = serviceRegistryDao;
}
@Audit(action = "DELETE_SERVICE",
actionResolverName = "DELETE_SERVICE_ACTION_RESOLVER",
resourceResolverName = "DELETE_SERVICE_RESOURCE_RESOLVER")
@Override
public synchronized RegisteredService delete(final long id) {
final RegisteredService service = findServiceBy(id);
if (service != null) {
this.serviceRegistryDao.delete(service);
this.services.remove(id);
this.orderedServices.remove(service);
publishEvent(new CasRegisteredServiceDeletedEvent(this, service));
}
return service;
}
@Override
public RegisteredService findServiceBy(final Service service) {
return orderedServices.stream().filter(r -> r.matches(service)).findFirst().orElse(null);
}
@Override
public Collection<RegisteredService> findServiceBy(final Predicate<RegisteredService> predicate) {
return orderedServices.stream()
.filter(predicate)
.collect(Collectors.toSet());
}
@Override
public <T extends RegisteredService> T findServiceBy(final Service serviceId, final Class<T> clazz) {
return findServiceBy(serviceId.getId(), clazz);
}
@Override
public <T extends RegisteredService> T findServiceBy(final String serviceId, final Class<T> clazz) {
return orderedServices.stream()
.filter(s -> s.getClass().isAssignableFrom(clazz) && s.matches(serviceId))
.map(clazz::cast)
.findFirst()
.orElse(null);
}
@Override
public RegisteredService findServiceBy(final long id) {
final RegisteredService r = this.services.get(id);
try {
return r == null ? null : r.clone();
} catch (final CloneNotSupportedException e) {
return r;
}
}
@Override
public Collection<RegisteredService> getAllServices() {
return Collections.unmodifiableCollection(orderedServices);
}
@Override
public boolean matchesExistingService(final Service service) {
return findServiceBy(service) != null;
}
@Audit(action = "SAVE_SERVICE",
actionResolverName = "SAVE_SERVICE_ACTION_RESOLVER",
resourceResolverName = "SAVE_SERVICE_RESOURCE_RESOLVER")
@Override
public synchronized RegisteredService save(final RegisteredService registeredService) {
final RegisteredService r = this.serviceRegistryDao.save(registeredService);
this.services.put(r.getId(), r);
this.orderedServices = new ConcurrentSkipListSet<>(this.services.values());
publishEvent(new CasRegisteredServiceSavedEvent(this, r));
return r;
}
/**
* Load services that are provided by the DAO.
*/
@Scheduled(initialDelayString = "${cas.serviceRegistry.startDelay:PT20S}",
fixedDelayString = "${cas.serviceRegistry.repeatInterval:PT60S}")
@Override
@PostConstruct
public void load() {
LOGGER.debug("Loading services from [{}]", this.serviceRegistryDao);
this.services = this.serviceRegistryDao.load().stream()
.collect(Collectors.toConcurrentMap(r -> {
LOGGER.debug("Adding registered service [{}]", r.getServiceId());
return r.getId();
}, r -> r, (r, s) -> s == null ? r : s));
this.orderedServices = new ConcurrentSkipListSet<>(this.services.values());
LOGGER.info("Loaded [{}] service(s) from [{}].", this.services.size(), this.serviceRegistryDao);
}
@Override
public RegisteredService findServiceBy(final String serviceId) {
return orderedServices.stream().filter(r -> r.matches(serviceId)).findFirst().orElse(null);
}
@Override
public boolean matchesExistingService(final String service) {
return findServiceBy(service) != null;
}
@Override
public int count() {
return services.size();
}
private void publishEvent(final ApplicationEvent event) {
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(event);
}
}
}