/** * 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.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import net.roboconf.core.model.beans.Instance; import net.roboconf.core.model.helpers.InstanceHelpers; import net.roboconf.core.model.helpers.VariableHelpers; import net.roboconf.core.utils.Utils; import net.roboconf.messaging.api.AbstractMessageProcessor; import net.roboconf.messaging.api.business.IAgentClient; import net.roboconf.messaging.api.business.ListenerCommand; import net.roboconf.messaging.api.extensions.IMessagingClient; import net.roboconf.messaging.api.extensions.MessagingContext; import net.roboconf.messaging.api.extensions.MessagingContext.RecipientKind; import net.roboconf.messaging.api.extensions.MessagingContext.ThoseThat; import net.roboconf.messaging.api.jmx.RoboconfMessageQueue; import net.roboconf.messaging.api.messages.Message; import net.roboconf.messaging.api.messages.from_agent_to_agent.MsgCmdAddImport; import net.roboconf.messaging.api.messages.from_agent_to_agent.MsgCmdRemoveImport; import net.roboconf.messaging.api.messages.from_agent_to_agent.MsgCmdRequestImport; import net.roboconf.messaging.api.messages.from_agent_to_dm.MsgNotifHeartbeat; import net.roboconf.messaging.api.utils.MessagingUtils; /** * @author Vincent Zurczak - Linagora */ public class ReconfigurableClientAgent extends ReconfigurableClient<IAgentClient> implements IAgentClient { private final ConcurrentHashMap<String,String> externalExports = new ConcurrentHashMap<> (); private String applicationName, scopedInstancePath, ipAddress; private boolean needsModel = false; // Methods inherited from ReconfigurableClient @Override protected void openConnection( IMessagingClient newMessagingClient ) throws IOException { newMessagingClient.setOwnerProperties( getOwnerKind(), this.domain, this.applicationName, this.scopedInstancePath ); newMessagingClient.openConnection(); listenToTheDm( newMessagingClient, ListenerCommand.START ); MsgNotifHeartbeat msg = new MsgNotifHeartbeat( this.applicationName, this.scopedInstancePath, this.ipAddress ); msg.setModelRequired( this.needsModel ); MessagingContext ctx = new MessagingContext( RecipientKind.DM, this.domain, this.applicationName ); newMessagingClient.publish( ctx, msg ); } @Override protected void configureMessageProcessor( AbstractMessageProcessor<IAgentClient> messageProcessor ) { messageProcessor.setMessagingClient( this ); } @Override public RecipientKind getOwnerKind() { return RecipientKind.AGENTS; } // Wrapping of the internal client @Override public void setMessageQueue( RoboconfMessageQueue messageQueue ) { getMessagingClient().setMessageQueue( messageQueue ); } @Override public boolean isConnected() { return getMessagingClient().isConnected(); } @Override public void openConnection() throws IOException { openConnection( getMessagingClient()); } @Override public void closeConnection() throws IOException { final IMessagingClient toClose = resetInternalClient(); if (toClose != null) toClose.closeConnection(); } @Override public void publishExports( Instance instance ) throws IOException { // For all the exported variables... // ... find the component or facet name... Set<String> names = VariableHelpers.findPrefixesForExportedVariables( instance ); if( names.isEmpty()) this.logger.fine( "Agent '" + getAgentId() + "' is publishing its exports." ); else for( String facetOrComponentName : names ) { publishExports( instance, facetOrComponentName ); } } @Override public void publishExports( Instance instance, String facetOrComponentName ) throws IOException { this.logger.fine( "Agent '" + getAgentId() + "' is publishing its exports prefixed by " + facetOrComponentName + "." ); // Find the variables to export. Map<String,String> toPublishInternally = new HashMap<> (); Map<String,String> toPublishExternally = new HashMap<> (); Map<String,String> exports = InstanceHelpers.findAllExportedVariables( instance ); for( Map.Entry<String,String> entry : exports.entrySet()) { // Publishing an export may be about a facet or component name or about an external prefix. // If an "internal prefix" is required, export both the internal and associated external, if any. String alias = this.externalExports.get( entry.getKey()); if( entry.getKey().startsWith( facetOrComponentName + "." )) { toPublishInternally.put( entry.getKey(), entry.getValue()); if( alias != null ) toPublishExternally.put( alias, entry.getValue()); } // If an external prefix is required, only export the external variables else if( alias != null && alias.startsWith( facetOrComponentName + "." )) { toPublishExternally.put( alias, entry.getValue()); } } // Publish the internal exports if( ! toPublishInternally.isEmpty()) { MsgCmdAddImport message = new MsgCmdAddImport( this.applicationName, facetOrComponentName, InstanceHelpers.computeInstancePath( instance ), toPublishInternally ); MessagingContext ctx = new MessagingContext( RecipientKind.AGENTS, this.domain, facetOrComponentName, ThoseThat.IMPORT, this.applicationName ); getMessagingClient().publish( ctx, message ); } // Publish the external ones, if any if( ! toPublishExternally.isEmpty()) { String varName = toPublishExternally.keySet().iterator().next(); String appTplName = VariableHelpers.parseVariableName( varName ).getKey(); MsgCmdAddImport message = new MsgCmdAddImport( this.applicationName, appTplName, InstanceHelpers.computeInstancePath( instance ), toPublishExternally ); MessagingContext ctx = new MessagingContext( RecipientKind.INTER_APP, this.domain, appTplName, ThoseThat.IMPORT, this.applicationName ); getMessagingClient().publish( ctx, message ); } } @Override public void unpublishExports( Instance instance ) throws IOException { this.logger.fine( "Agent '" + getAgentId() + "' is un-publishing its exports." ); // For all the exported variables... // ... find the component or facet name... for( MessagingContext ctx : MessagingContext.forExportedVariables( this.domain, this.applicationName, instance, this.externalExports, ThoseThat.IMPORT )) { // Log here, for debug this.logger.fine( "Agent '" + getAgentId() + "' is un-publishing its exports (" + ctx + ")." ); // Un-publish them MsgCmdRemoveImport message = new MsgCmdRemoveImport( this.applicationName, ctx.getComponentOrFacetName(), InstanceHelpers.computeInstancePath( instance )); getMessagingClient().publish( ctx, message ); } } @Override public void listenToRequestsFromOtherAgents( ListenerCommand command, Instance instance ) throws IOException { // Find the right contexts to subscribe. // This depends on the exported variables. for( MessagingContext ctx : MessagingContext.forExportedVariables( this.domain, this.applicationName, instance, this.externalExports, ThoseThat.EXPORT )) { if( command == ListenerCommand.START ) { this.logger.fine( "Agent '" + getAgentId() + "' starts listening requests from other agents (" + ctx + ")." ); getMessagingClient().subscribe( ctx ); } else { this.logger.fine( "Agent '" + getAgentId() + "' stops listening requests from other agents (" + ctx + ")." ); getMessagingClient().unsubscribe( ctx ); } } } @Override public void requestExportsFromOtherAgents( Instance instance ) throws IOException { this.logger.fine( "Agent '" + getAgentId() + "' is requesting exports from other agents." ); // For all the imported variables... // ... find the component or facet name... for( MessagingContext ctx : MessagingContext.forImportedVariables( this.domain, this.applicationName, instance, ThoseThat.EXPORT )) { // Log here, for debug this.logger.fine( "Agent '" + getAgentId() + "' is requesting exports from other agents (" + ctx + ")." ); // ... and ask to publish them. // Grouping variable requests by prefix reduces the number of messages. MsgCmdRequestImport message = new MsgCmdRequestImport( this.applicationName, ctx.getComponentOrFacetName()); getMessagingClient().publish( ctx, message ); } } @Override public void listenToExportsFromOtherAgents( ListenerCommand command, Instance instance ) throws IOException { // With RabbitMQ, and for agents, listening to others means // create a binding between the "agents" exchange and the agent's queue. for( MessagingContext ctx : MessagingContext.forImportedVariables( this.domain, this.applicationName, instance, ThoseThat.IMPORT )) { // On which routing key do export go? Those.that.import... if( command == ListenerCommand.START ) { this.logger.fine( "Agent '" + getAgentId() + "' starts listening exports from other agents (" + ctx + ")." ); getMessagingClient().subscribe( ctx ); } else { this.logger.fine( "Agent '" + getAgentId() + "' stops listening exports from other agents (" + ctx + ")." ); getMessagingClient().unsubscribe( ctx ); } } } @Override public void sendMessageToTheDm( Message message ) throws IOException { // The context match the one used by the DM to listen to messages sent by agents. this.logger.fine( "Agent '" + getAgentId() + "' is sending a " + message.getClass().getSimpleName() + " message to the DM." ); MessagingContext ctx = new MessagingContext( RecipientKind.DM, this.domain, this.applicationName ); getMessagingClient().publish( ctx, message ); } @Override public void listenToTheDm( ListenerCommand command ) throws IOException { listenToTheDm( getMessagingClient(), command ); } private void listenToTheDm( IMessagingClient client, ListenerCommand command ) throws IOException { // The context match the one used by the DM to send a message to an agent. // The agent client MUST have a scoped instance path! String topicName = MessagingUtils.buildTopicNameForAgent( this.scopedInstancePath ); MessagingContext ctx = new MessagingContext( RecipientKind.AGENTS, this.domain, topicName, this.applicationName ); if( command == ListenerCommand.START ) { this.logger.fine( "Agent '" + getAgentId() + "' starts listening to the DM." ); client.subscribe( ctx ); } else { this.logger.fine( "Agent '" + getAgentId() + "' stops listening to the DM." ); client.unsubscribe( ctx ); } } // Setter methods @Override public void setApplicationName( String applicationName ) { this.applicationName = applicationName; // Propagate the information to the internal client. getMessagingClient().setOwnerProperties( getOwnerKind(), this.domain, applicationName, this.scopedInstancePath ); } @Override public void setScopedInstancePath( String scopedInstancePath ) { this.scopedInstancePath = scopedInstancePath; // Propagate the information to the internal client. getMessagingClient().setOwnerProperties( getOwnerKind(), this.domain, this.applicationName, scopedInstancePath ); } @Override public void setExternalMapping( Map<String,String> externalExports ) { this.externalExports.clear(); if( externalExports != null ) this.externalExports.putAll( externalExports ); } public void setIpAddress( String ipAddress ) { this.ipAddress = ipAddress; } public void setNeedsModel( boolean needsModel ) { this.needsModel = needsModel; } private String getAgentId() { return Utils.isEmptyOrWhitespaces( this.scopedInstancePath ) ? "?" : this.scopedInstancePath; } }