package com.kryptnostic.rhizome.configuration.service; import java.io.IOException; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.eventbus.AsyncEventBus; import com.hazelcast.core.IMap; import com.hazelcast.core.ITopic; import com.hazelcast.core.Message; import com.hazelcast.core.MessageListener; import com.kryptnostic.rhizome.configuration.ConfigurationKey; public class RhizomeConfigurationService extends AbstractYamlConfigurationService implements MessageListener<Object> { private static final Logger logger = LoggerFactory .getLogger( RhizomeConfigurationService.class ); protected final IMap<ConfigurationKey, String> configurations; protected final ITopic<Object> configurationsTopic; public RhizomeConfigurationService( IMap<ConfigurationKey, String> configurations, ITopic<Object> configurationsTopic, AsyncEventBus configurationUpdates ) { super( configurationUpdates ); this.configurationsTopic = configurationsTopic; this.configurations = Preconditions.checkNotNull( configurations, "Configurations map cannot be null." ); configurationsTopic.addMessageListener( this ); } @Override public @Nullable <T> T getConfiguration( Class<T> clazz ) { ConfigurationKey key = ConfigurationService.StaticLoader.getConfigurationKey( clazz ); // If we end up with a null key log an error if ( key == null ) { logger.error( "Unable to load key for configuration class {}", clazz.getName() ); return null; } /* * If the Configurations map isn't available load from resource. Generally, this will be for Configurations that * need to be loaded in order to start. */ T s = null; if ( configurations != null ) { String str = configurations.get( key ); if ( StringUtils.isNotBlank( str ) ) { try { s = mapper.readValue( str, clazz ); } catch ( IOException e ) { logger.error( "Failed to read Configuration " + key.getUri(), e ); } } } /* * If we don't have the Configuration available attempt to load a missing Configuration from disk, using the id, * which is really the context path. */ if ( s == null ) { logger.debug( "Configuration key {} value unavailable. Attempting to load from disk.", key ); s = ConfigurationService.StaticLoader.loadConfigurationFromResource( key, clazz ); } else { return s; } /* * If we successfully read in the Configuration and we have the ability to persist the Configuration do so. */ if ( s != null & configurations != null ) { try { configurations.put( key, mapper.writeValueAsString( s ) ); } catch ( JsonProcessingException e ) { logger.error( "Unable to marshal value from disk " + key.getUri(), e ); } } return s; } @Override public <T> void setConfiguration( T configuration ) { ConfigurationKey key = ConfigurationService.StaticLoader.getConfigurationKey( configuration.getClass() ); try { persistConfiguration( key, mapper.writeValueAsString( configuration ) ); configurationsTopic.publish( configuration ); } catch ( JsonProcessingException e ) { logger.error( "Unable to set configuration {} = {}", key, configuration, e ); } } @Override public void onMessage( Message<Object> message ) { /* * This is a light weight operation, that hands off to the async event bus thread pool. It will trigger a post * that asynchronously notifies all subscribers that the configuration has been updated. It will not trigger a * re-persist of information. */ if ( message.getMessageObject() != null ) { String uri = ""; if ( logger.isDebugEnabled() ) { uri = ConfigurationService.StaticLoader.getConfigurationKey( message.getMessageObject().getClass() ) .getUri(); } logger.debug( "Updating Configuration {}", uri ); post( message.getMessageObject() ); logger.debug( "Successfully updated Configuration {}", uri ); } } @Override protected @Nullable String fetchConfiguration( ConfigurationKey key ) { return Strings.emptyToNull( configurations.get( key ) ); } @Override protected void persistConfiguration( ConfigurationKey key, String configurationYaml ) { configurations.put( key, configurationYaml ); } public ObjectMapper getMapper() { return mapper; } }