/**
* Copyright 2014-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* 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 net.roboconf.messaging.rabbitmq.internal;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.DEFAULT_IP;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.FACTORY_RABBITMQ;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.GUEST;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SERVER_IP;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SERVER_PASSWORD;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SERVER_USERNAME;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_USE_SSL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import net.roboconf.core.utils.Utils;
import net.roboconf.messaging.api.MessagingConstants;
import net.roboconf.messaging.api.extensions.IMessagingClient;
import net.roboconf.messaging.api.factory.IMessagingClientFactory;
import net.roboconf.messaging.api.reconfigurables.ReconfigurableClient;
import net.roboconf.messaging.rabbitmq.RabbitMqConstants;
/**
* Messaging client factory for Rabbit MQ.
* @author Pierre Bourret - Université Joseph Fourier
*/
public class RabbitMqClientFactory implements IMessagingClientFactory {
// The connection properties.
Map<String,String> configuration = Collections.emptyMap();
// The created clients.
// References to the clients are *weak*, so we never prevent their garbage collection.
final Set<RabbitMqClient> clients = Collections.newSetFromMap( new WeakHashMap<RabbitMqClient,Boolean> ());
// The logger
private final Logger logger = Logger.getLogger( this.getClass().getName());
/**
* Reconfigures the client.
*/
public void reconfigure() {
this.logger.fine( "Rabbit MQ clients are about to be reconfigured." );
// Set the properties for all created clients.
resetClients( false );
}
/**
* Stops the client (invoked by iPojo).
*/
public void stop() {
resetClients(true);
}
private void resetClients( boolean shutdown ) {
// Make fresh snapshots of the clients, as we don't want to reconfigure them while holding the lock.
final ArrayList<RabbitMqClient> clients;
synchronized( this ) {
// Get the snapshot.
clients = new ArrayList<>( this.clients );
// Remove the clients, new ones will be created if necessary.
this.clients.clear();
}
// Now reconfigure all the clients.
for( RabbitMqClient client : clients ) {
try {
final ReconfigurableClient<?> reconfigurable = client.getReconfigurableClient();
// The reconfigurable can never be null.
// If it was, a NPE would have been thrown when the Rabbit MQ client was created.
if( shutdown )
reconfigurable.closeConnection();
else
reconfigurable.switchMessagingType( RabbitMqConstants.FACTORY_RABBITMQ );
} catch( Throwable t ) {
// Warn but continue to reconfigure the next clients!
this.logger.warning("A client has thrown an exception on reconfiguration: " + client);
Utils.logException(this.logger, new RuntimeException(t));
}
}
}
@Override
public String getType() {
return RabbitMqConstants.FACTORY_RABBITMQ;
}
@Override
public IMessagingClient createClient( final ReconfigurableClient<?> parent ) {
this.logger.fine( "Creating a new Rabbit MQ client with owner = " + parent.getOwnerKind());
// The parent cannot be null. A NPE MUST be thrown otherwise.
// That's what the RabbitMqClient constructor does. There is unit test for this.
final RabbitMqClient client = new RabbitMqClient( parent, this.configuration );
synchronized( this ) {
this.clients.add( client );
}
this.logger.finer( "A new Rabbit MQ client was created." );
return client;
}
/**
* Invoked by iPojo when one or several properties were updated from Config Admin.
* @param properties
*/
public void setConfiguration( Dictionary<?,?> properties ) {
// Ignore iPojo properties
final List<String> propertiesToSkip = Arrays.asList(
"component",
"felix.fileinstall.filename" );
// Convert the dictionary into a map
Map<String,String> map = new LinkedHashMap<> ();
for( Enumeration<?> en = properties.keys(); en.hasMoreElements(); ) {
Object key = en.nextElement();
String keyAsString = String.valueOf( key );
if( propertiesToSkip.contains( keyAsString ))
continue;
// "null" are not acceptable values in dictionaries
// (OSGi often use Hash tables)
Object value = properties.get( key );
map.put( keyAsString, String.valueOf( value ));
}
// Invoked by iPojo => the messaging type is the right one in this method
map.put( MessagingConstants.MESSAGING_TYPE_PROPERTY, FACTORY_RABBITMQ );
setConfiguration( map );
}
@Override
public boolean setConfiguration( final Map<String,String> configuration ) {
boolean result = false;
final String type = configuration.get( MessagingConstants.MESSAGING_TYPE_PROPERTY ) ;
if(( result = FACTORY_RABBITMQ.equals( type ))) {
Map<String,String> configurationWithDefaults = new LinkedHashMap<> ();
configurationWithDefaults.put( RABBITMQ_SERVER_IP, DEFAULT_IP );
configurationWithDefaults.put( RABBITMQ_SERVER_USERNAME, GUEST );
configurationWithDefaults.put( RABBITMQ_SERVER_PASSWORD, GUEST );
configurationWithDefaults.put( RABBITMQ_USE_SSL, Boolean.FALSE.toString());
configurationWithDefaults.putAll( configuration );
// Avoid unnecessary (and potentially problematic) reconfiguration if nothing has changed.
// First we detect for changes, and set the parameters accordingly.
boolean hasChanged = false;
synchronized( this ) {
for( Map.Entry<String,String> configEntry : configurationWithDefaults.entrySet()) {
String value = this.configuration.get( configEntry.getKey());
if( ! Objects.equals( value, configEntry.getValue())) {
hasChanged = true;
break;
}
}
if( hasChanged ) {
this.configuration = configurationWithDefaults;
this.logger.finer( "Updating the messaging properties." );
}
}
// Then, if changes has occurred, we reconfigure the factory. This will invalidate every created client.
// Otherwise, if nothing has changed, we do nothing. Thus we avoid invalidating clients uselessly, and
// prevent any message loss.
if( hasChanged )
reconfigure();
}
return result;
}
}