/** * 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.api.reconfigurables; import java.io.IOException; import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.logging.Logger; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import net.roboconf.core.utils.Utils; import net.roboconf.messaging.api.AbstractMessageProcessor; import net.roboconf.messaging.api.MessagingConstants; import net.roboconf.messaging.api.business.IClient; import net.roboconf.messaging.api.extensions.IMessagingClient; import net.roboconf.messaging.api.extensions.MessagingContext.RecipientKind; import net.roboconf.messaging.api.factory.IMessagingClientFactory; import net.roboconf.messaging.api.factory.MessagingClientFactoryListener; import net.roboconf.messaging.api.factory.MessagingClientFactoryRegistry; import net.roboconf.messaging.api.internal.jmx.JmxWrapperForMessagingClient; import net.roboconf.messaging.api.utils.OsgiHelper; /** * A class that can switch dynamically between messaging types. * @param <T> a sub-class of {@link IMessagingClient} * @author Vincent Zurczak - Linagora */ public abstract class ReconfigurableClient<T extends IClient> implements IClient, MessagingClientFactoryListener { protected final Logger logger = Logger.getLogger( getClass().getName()); private AbstractMessageProcessor<T> messageProcessor; private String messagingType; private JmxWrapperForMessagingClient messagingClient; private MessagingClientFactoryRegistry registry; protected String domain; PrintStream console = System.out; /** * Constructor. */ protected ReconfigurableClient() { resetInternalClient(); // Try to find the MessagingClientFactoryRegistry service. setRegistry( lookupMessagingClientFactoryRegistryService( new OsgiHelper())); } @Override public void setDomain( String domain ) { this.domain = domain; } @Override public String getDomain() { return this.domain; } /** * @return the {@code MessagingClientFactoryRegistry} associated to this client. */ public synchronized MessagingClientFactoryRegistry getRegistry() { return this.registry; } /** * Sets the {@code MessagingClientFactoryRegistry} associated for this client. * @param registry the {@code MessagingClientFactoryRegistry} for this client. */ public synchronized void setRegistry( MessagingClientFactoryRegistry registry ) { if( this.registry != null ) this.registry.removeListener(this); this.registry = registry; if (registry != null) registry.addListener(this); } /** * Try to locate the {@code MessagingClientFactoryRegistry} service in an OSGi execution context. * <p> * NOTE: this method is, by definition, quite dirty. * </p> * TODO: what happens when the registry component is being started, but the service is not yet registered. Wait? How long? * @return the located {@code MessagingClientFactoryRegistry} service, or {@code null} if the service cannot be * found, or if there is no OSGi execution context. */ public static MessagingClientFactoryRegistry lookupMessagingClientFactoryRegistryService( OsgiHelper osgiHelper ) { MessagingClientFactoryRegistry result = null; final Logger logger = Logger.getLogger( ReconfigurableClient.class.getName()); BundleContext bundleCtx = osgiHelper.findBundleContext(); if( bundleCtx != null ) { logger.info( "The messaging registry is used in an OSGi environment." ); // There must be only *one* MessagingClientFactoryRegistry service. final ServiceReference<MessagingClientFactoryRegistry> reference = bundleCtx.getServiceReference( MessagingClientFactoryRegistry.class ); // The service will be unget when this bundle stops. No need to worry! if( reference != null ) { logger.fine( "The service reference was found." ); result = bundleCtx.getService( reference ); } } else { logger.info( "The messaging registry is NOT used in an OSGi environment." ); } return result; } @Override public synchronized String getMessagingType() { return this.messagingType; } /** * Changes the internal messaging client. * @param factoryName the factory name (see {@link MessagingConstants}) */ public void switchMessagingType( String factoryName ) { // Create a new client this.logger.fine( "The messaging is requested to switch its type to " + factoryName + "." ); JmxWrapperForMessagingClient newMessagingClient = null; try { IMessagingClient rawClient = createMessagingClient( factoryName ); if( rawClient != null ) { newMessagingClient = new JmxWrapperForMessagingClient( rawClient ); newMessagingClient.setMessageQueue( this.messageProcessor.getMessageQueue()); openConnection( newMessagingClient ); } } catch( Exception e ) { this.logger.warning( "An error occurred while creating a new messaging client. " + e.getMessage()); Utils.logException( this.logger, e ); // #594: print a message to be visible in a console StringBuilder sb = new StringBuilder(); sb.append( "\n\n**** WARNING ****\n" ); sb.append( "Connection failed at " ); sb.append( new SimpleDateFormat( "HH:mm:ss, 'on' EEEE dd (MMMM)" ).format( new Date())); sb.append( ".\n" ); sb.append( "The messaging configuration may be invalid.\n" ); sb.append( "Or the messaging server may not be started yet.\n\n" ); sb.append( "Consider using the 'roboconf:force-reconnect' command if you forgot to start the messaging server.\n" ); sb.append( "**** WARNING ****\n" ); this.console.println( sb.toString()); } // Replace the current client IMessagingClient oldClient; synchronized( this ) { // Simple changes this.messagingType = factoryName; oldClient = this.messagingClient; // The messaging client can NEVER be null if( newMessagingClient != null ) this.messagingClient = newMessagingClient; else resetInternalClient(); } terminateClient( oldClient, "The previous client could not be terminated correctly.", this.logger ); } @Override public void addMessagingClientFactory( final IMessagingClientFactory factory ) { this.logger.fine( "A new messaging factory was added: " + factory.getType()); synchronized( this ) { if( this.messagingClient.isDismissClient() && factory.getType().equals( this.messagingType )) { // This is the messaging factory we were expecting... // We can try to switch to this incoming factory right now! switchMessagingType( this.messagingType ); } } } @Override public void removeMessagingClientFactory( IMessagingClientFactory factory ) { String factoryType = factory != null ? factory.getType() : null; IMessagingClient oldClient = null; synchronized( this ) { if( this.messagingClient != null && this.messagingClient.getMessagingType().equals( factoryType )) { // This is the messaging factory we were using... // We must release our messaging client right now. oldClient = resetInternalClient(); } } terminateClient( oldClient, "The previous client could not be terminated correctly.", this.logger ); this.logger.fine( "A messaging factory was removed: " + factoryType ); } @Override public Map<String,String> getConfiguration() { final Map<String,String> result; synchronized( this ) { result = this.messagingClient.getConfiguration(); } return result; } /** * Creates a new messaging client. * @param factoryName the factory name (see {@link MessagingConstants}) * @return a new messaging client, or {@code null} if {@code factoryName} is {@code null} or cannot be found in the * available messaging factories. * @throws IOException if something went wrong */ protected IMessagingClient createMessagingClient( String factoryName ) throws IOException { IMessagingClient client = null; MessagingClientFactoryRegistry registry = getRegistry(); if( registry != null ) { IMessagingClientFactory factory = registry.getMessagingClientFactory(factoryName); if( factory != null ) client = factory.createClient( this ); } return client; } /** * Configures a newly created client. * <p> * For an agent, it consists into listening to messages coming from the DM. * </p> * <p> * For the DM, it consists in listening to messages coming from various agents. * </p> * * @param newMessagingClient the messaging client to configure * @throws IOException if something went wrong */ protected abstract void openConnection( IMessagingClient newMessagingClient ) throws IOException; /** * Configures the message processor. * @param messageProcessor the message processor */ protected abstract void configureMessageProcessor( AbstractMessageProcessor<T> messageProcessor ); /** * @return the kind of this client's owner (DM or AGENT). */ public abstract RecipientKind getOwnerKind(); /** * Associates a message processor with this instance. * <p> * The message processor cannot be started before. This method will start it. * </p> * <p> * This method must be invoked only once. * </p> * @param messageProcessor the message processor */ public void associateMessageProcessor( AbstractMessageProcessor<T> messageProcessor ) { if( this.messageProcessor != null ) throw new IllegalArgumentException( "The message processor was already defined." ); this.messageProcessor = messageProcessor; configureMessageProcessor( messageProcessor ); this.messageProcessor.start(); } /** * @return the message processor */ public AbstractMessageProcessor<T> getMessageProcessor() { return this.messageProcessor; } /** * @return true if the internal client exists and is connected, false otherwise */ public synchronized boolean hasValidClient() { // The dismissed client always return false for this statement. return this.messagingClient.isConnected(); } /** * @return the messagingClient */ public IMessagingClient getMessagingClient() { return this.messagingClient; } /** * Resets the internal client. */ protected synchronized IMessagingClient resetInternalClient() { IMessagingClient oldClient = this.messagingClient; this.messagingClient = new JmxWrapperForMessagingClient( null ); return oldClient; } /** * Closes the connection of a messaging client and terminates it properly. * @param client the client (never null) * @param errorMessage the error message to log in case of problem * @param logger a logger */ static void terminateClient( IMessagingClient client, String errorMessage, Logger logger ) { try { logger.fine( "The reconfigurable client is requesting its internal connection to be closed." ); client.closeConnection(); } catch( Exception e ) { logger.warning( errorMessage + " " + e.getMessage()); Utils.logException( logger, e ); } finally { // "unregisterService" was not merged with "closeConnection" // on purpose. What is specific to JMX is restricted to this class // and this bundle. Sub-classes may use "closeConnection" without // any side effect on the JMX part. if( client instanceof JmxWrapperForMessagingClient ) ((JmxWrapperForMessagingClient) client).unregisterService(); } } }