package org.apereo.cas.config;
import com.google.common.base.Throwables;
import com.hazelcast.config.Config;
import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MaxSizeConfig;
import com.hazelcast.config.MulticastConfig;
import com.hazelcast.config.NetworkConfig;
import com.hazelcast.config.TcpIpConfig;
import com.hazelcast.config.XmlConfigBuilder;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.hazelcast.HazelcastProperties;
import org.apereo.cas.configuration.support.Beans;
import org.apereo.cas.ticket.TicketCatalog;
import org.apereo.cas.ticket.TicketDefinition;
import org.apereo.cas.ticket.registry.HazelcastTicketRegistry;
import org.apereo.cas.ticket.registry.NoOpTicketRegistryCleaner;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.ticket.registry.TicketRegistryCleaner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Spring's Java configuration component for {@code HazelcastInstance} that is consumed and used by
* {@link HazelcastTicketRegistry}.
* <p>
* This configuration class has the smarts to choose the configuration source for the {@link HazelcastInstance}
* that it produces by either loading the native hazelcast XML config file from a resource location
* or it creates the {@link HazelcastInstance} programmatically
* with a handful properties and their defaults (if not set) that it exposes to CAS deployers.
*
* @author Misagh Moayyed
* @author Dmitriy Kopylenko
* @since 4.2.0
*/
@Configuration("hazelcastTicketRegistryConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class HazelcastTicketRegistryConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastTicketRegistryConfiguration.class);
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Bean
public TicketRegistry ticketRegistry(@Qualifier("ticketCatalog") final TicketCatalog ticketCatalog) {
final HazelcastProperties hz = casProperties.getTicket().getRegistry().getHazelcast();
final HazelcastTicketRegistry r = new HazelcastTicketRegistry(hazelcast(ticketCatalog),
ticketCatalog,
hz.getPageSize());
r.setCipherExecutor(Beans.newTicketRegistryCipherExecutor(hz.getCrypto()));
return r;
}
@Autowired
@Bean
public TicketRegistryCleaner ticketRegistryCleaner(@Qualifier("ticketCatalog") final TicketCatalog ticketCatalog) {
return new NoOpTicketRegistryCleaner();
}
@Autowired
@Bean
public HazelcastInstance hazelcast(@Qualifier("ticketCatalog") final TicketCatalog ticketCatalog) {
return Hazelcast.newHazelcastInstance(getConfig(ticketCatalog));
}
private Config getConfig(final TicketCatalog ticketCatalog) {
final HazelcastProperties hz = casProperties.getTicket().getRegistry().getHazelcast();
final HazelcastProperties.Cluster cluster = hz.getCluster();
final Config config;
if (hz.getConfigLocation() != null && hz.getConfigLocation().exists()) {
try {
final URL configUrl = hz.getConfigLocation().getURL();
LOGGER.debug("Loading Hazelcast configuration from [{}]", configUrl);
config = new XmlConfigBuilder(hz.getConfigLocation().getInputStream()).build();
config.setConfigurationUrl(configUrl);
} catch (final Exception e) {
throw Throwables.propagate(e);
}
} else {
// No config location, so do a default config programmatically with handful of properties exposed by CAS
config = new Config();
config.setProperty("hazelcast.prefer.ipv4.stack", String.valueOf(cluster.isIpv4Enabled()));
// TCP config
final TcpIpConfig tcpIpConfig = new TcpIpConfig()
.setEnabled(cluster.isTcpipEnabled())
.setMembers(cluster.getMembers())
.setConnectionTimeoutSeconds(cluster.getTimeout());
LOGGER.debug("Created Hazelcast TCP/IP configuration [{}]", tcpIpConfig);
// Multicast config
final MulticastConfig multicastConfig = new MulticastConfig().setEnabled(cluster.isMulticastEnabled());
if (cluster.isMulticastEnabled()) {
multicastConfig.setMulticastGroup(cluster.getMulticastGroup());
multicastConfig.setMulticastPort(cluster.getMulticastPort());
final Set<String> trustedInterfaces = StringUtils.commaDelimitedListToSet(cluster.getMulticastTrustedInterfaces());
if (!trustedInterfaces.isEmpty()) {
multicastConfig.setTrustedInterfaces(trustedInterfaces);
}
multicastConfig.setMulticastTimeoutSeconds(cluster.getMulticastTimeout());
multicastConfig.setMulticastTimeToLive(cluster.getMulticastTimeToLive());
}
LOGGER.debug("Created Hazelcast Multicast configuration [{}]", multicastConfig);
// Join config
final JoinConfig joinConfig = new JoinConfig()
.setMulticastConfig(multicastConfig)
.setTcpIpConfig(tcpIpConfig);
LOGGER.debug("Created Hazelcast join configuration [{}]", joinConfig);
// Network config
final NetworkConfig networkConfig = new NetworkConfig()
.setPort(cluster.getPort())
.setPortAutoIncrement(cluster.isPortAutoIncrement())
.setJoin(joinConfig);
LOGGER.debug("Created Hazelcast network configuration [{}]", networkConfig);
// Finally aggregate all those config into the main Config
config.setMapConfigs(buildHazelcastMapConfigurations(ticketCatalog)).setNetworkConfig(networkConfig);
}
// Add additional default config properties regardless of the configuration source
return config.setInstanceName(cluster.getInstanceName())
.setProperty(HazelcastProperties.LOGGING_TYPE_PROP, cluster.getLoggingType())
.setProperty(HazelcastProperties.MAX_HEARTBEAT_SECONDS_PROP, String.valueOf(cluster.getMaxNoHeartbeatSeconds()));
}
private Map<String, MapConfig> buildHazelcastMapConfigurations(final TicketCatalog ticketCatalog) {
final Map<String, MapConfig> mapConfigs = new HashMap<>();
final Collection<TicketDefinition> definitions = ticketCatalog.findAll();
definitions.forEach(t -> {
final MapConfig mapConfig = createMapConfig(t);
LOGGER.debug("Created Hazelcast map configuration for [{}]", t);
mapConfigs.put(t.getProperties().getStorageName(), mapConfig);
});
return mapConfigs;
}
private MapConfig createMapConfig(final TicketDefinition definition) {
final HazelcastProperties hz = casProperties.getTicket().getRegistry().getHazelcast();
final HazelcastProperties.Cluster cluster = hz.getCluster();
final EvictionPolicy evictionPolicy = EvictionPolicy.valueOf(cluster.getEvictionPolicy());
LOGGER.debug("Creating Hazelcast map configuration for [{}] with idle timeout [{}] second(s)",
definition.getProperties().getStorageName(), definition.getProperties().getStorageTimeout());
return new MapConfig()
.setName(definition.getProperties().getStorageName())
.setMaxIdleSeconds((int) definition.getProperties().getStorageTimeout())
.setBackupCount(cluster.getBackupCount())
.setAsyncBackupCount(cluster.getAsyncBackupCount())
.setEvictionPolicy(evictionPolicy)
.setMaxSizeConfig(new MaxSizeConfig()
.setMaxSizePolicy(MaxSizeConfig.MaxSizePolicy.valueOf(cluster.getMaxSizePolicy()))
.setSize(cluster.getMaxHeapSizePercentage()));
}
}