001    // jademx - JADE management using JMX
002    // Copyright 2005 Caboodle Networks, Inc.
003    //
004    // This library is free software; you can redistribute it and/or
005    // modify it under the terms of the GNU Lesser General Public
006    // License as published by the Free Software Foundation; either
007    // version 2.1 of the License, or (at your option) any later version.
008    //
009    // This library is distributed in the hope that it will be useful,
010    // but WITHOUT ANY WARRANTY; without even the implied warranty of
011    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
012    // Lesser General Public License for more details.
013    //
014    // You should have received a copy of the GNU Lesser General Public
015    // License along with this library; if not, write to the Free Software
016    // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
017    
018    package jade.jademx.agent;
019    
020    import jade.content.ContentManager;
021    import jade.content.lang.Codec;
022    import jade.content.lang.sl.SLCodec;
023    import jade.content.onto.BasicOntology;
024    import jade.content.onto.Ontology;
025    import jade.content.onto.OntologyException;
026    import jade.content.onto.basic.Action;
027    import jade.content.onto.basic.Result;
028    import jade.content.schema.ConceptSchema;
029    import jade.core.AID;
030    import jade.core.Agent;
031    import jade.domain.FIPANames;
032    import jade.domain.FIPAAgentManagement.NotUnderstoodException;
033    import jade.domain.FIPAAgentManagement.RefuseException;
034    import jade.jademx.util.MBeanUtil;
035    import jade.lang.acl.ACLMessage;
036    import jade.lang.acl.MessageTemplate;
037    import jade.proto.SimpleAchieveREInitiator;
038    import jade.proto.SimpleAchieveREResponder;
039    import jade.util.Logger;
040    
041    import javax.management.Attribute;
042    import javax.management.AttributeNotFoundException;
043    import javax.management.InvalidAttributeValueException;
044    import javax.management.MBeanAttributeInfo;
045    import javax.management.MBeanConstructorInfo;
046    import javax.management.MBeanException;
047    import javax.management.MBeanInfo;
048    import javax.management.MBeanNotificationInfo;
049    import javax.management.MBeanOperationInfo;
050    import javax.management.MBeanParameterInfo;
051    import javax.management.Notification;
052    import javax.management.ReflectionException;
053    
054    /** 
055     * sample agent to demonstrate building agent using jademx.
056     * @author David Bernstein, <a href="http://www.caboodlenetworks.com"
057     *  >Caboodle Networks, Inc.</a>
058     */
059    public class JademxPingAgent extends JademxAgent {
060    
061        /**
062         * make a Jademx ping agent 
063         */
064        public JademxPingAgent() {
065            super();
066            mBeanInfoThisLevel = constructMBeanInfo();
067            logger.log(Logger.FINE,"made MBeanInfo "+mBeanInfoThisLevel);
068            mBeanInfoSuper = super.getMBeanInfo();
069            mBeanInfoMerged = MBeanUtil.mergeMBeanInfo( 
070                    getClass().getName(), DESCRIPTION, 
071                    mBeanInfoThisLevel, mBeanInfoSuper );
072            logger.log(Logger.FINE,"made merged MBeanInfo "+mBeanInfoMerged);
073            //setMBeanInfo( constructMBeanInfo() );
074        }
075    
076        /** default answer */
077        public final static String DEFAULT_ANSWER = "pong";
078        /**
079         * response to a ping.
080         * access must be synchronized because it's accessed both by the
081         * MBeanServer thread and by the JADE agent thread.  
082         */
083        private String response = DEFAULT_ANSWER;
084        /** my logger */
085        private final Logger logger = 
086            Logger.getMyLogger(JademxPingAgent.class.getName());
087        /** my codec */
088        private Codec codec = new SLCodec();
089        /** my ontology */
090        private Ontology ontology = PingOntology.getInstance();
091        /** my content manager */
092        private ContentManager contentManager = null;
093        
094        // MBEAN FEATURE INFO
095        
096        // attributes
097        
098        /** response attribute name */
099        public final static String ATTR_RESPONSE_NAME = "Response";
100        /** response attribute description */
101        private final static String ATTR_RESPONSE_DESC = "ping response";
102        /** response attribute type */
103        private final static String ATTR_RESPONSE_TYPE = String.class.getName();
104        
105        // operations
106        
107        /** ping operation name */
108        public final static String OPER_PING_NAME = "ping";
109        /** ping operation description */
110        private final static String OPER_PING_DESC = "ping another agent";
111        /** ping agentFullName parameter name */
112        private final static String OPER_PING_AGENT_FULL_NAME_PARM_NAME = 
113            "agentFullName";
114        /** ping agentFullName parameter type */
115        private final static String OPER_PING_AGENT_FULL_NAME_PARM_TYPE = 
116            String.class.getName();
117        /** ping agentFullName parameter description */
118        private final static String OPER_PING_AGENT_FULL_NAME_PARM_DESC = 
119            "full JADE name of agent that pinged this agent";
120        /** signature for a ping operation */
121        public final static String OPER_PING_SIGNATURE[] = { 
122                OPER_PING_AGENT_FULL_NAME_PARM_TYPE 
123        };
124        /** return type for ping operation */
125        public final static String OPER_PING_TYPE = String.class.getName();
126        
127        // nofifications
128        
129        /** notification that have been pinged */
130        public final static String NOTIF_PINGED_NAME = "pinged";
131    
132        
133        // END MBEAN FEATURE INFO
134    
135        /** description for MBeanInfo */
136        private final static String DESCRIPTION = 
137            "sample ping agent under Jademx control";
138        
139        
140        /** MBeanInfo for this class and superclass(es) */
141        private MBeanInfo mBeanInfoMerged = null;
142        /** MBeanInfo for superclass(es) */
143        private MBeanInfo mBeanInfoSuper = null;
144        /** MBeanInfo for for this class but not superclass(es) */
145        private MBeanInfo mBeanInfoThisLevel = null;
146        
147        /**
148         * create a new MBeanInfo object to describe this agent.
149         * @return new MBeanInfo object to describe this agent.
150         */
151        private MBeanInfo constructMBeanInfo() {
152            
153            // attributes
154    
155            MBeanAttributeInfo aI[] = new MBeanAttributeInfo[] {
156                    new MBeanAttributeInfo( 
157                            ATTR_RESPONSE_NAME, 
158                            ATTR_RESPONSE_TYPE, 
159                            ATTR_RESPONSE_DESC,
160                            true, true, false )
161            };
162            
163            // constructors
164    
165            MBeanConstructorInfo cI[] = new MBeanConstructorInfo[0];
166            
167            // operations
168    
169            MBeanParameterInfo pIPing[] = { 
170                    new MBeanParameterInfo( 
171                            OPER_PING_AGENT_FULL_NAME_PARM_NAME, 
172                            OPER_PING_AGENT_FULL_NAME_PARM_TYPE, 
173                            OPER_PING_AGENT_FULL_NAME_PARM_DESC) 
174            };
175            MBeanOperationInfo oI[] = new MBeanOperationInfo[] {
176                    new MBeanOperationInfo( 
177                            OPER_PING_NAME, 
178                            OPER_PING_DESC, 
179                            pIPing, 
180                            OPER_PING_TYPE, 
181                            MBeanOperationInfo.INFO )
182            };
183            
184            // notifications
185            
186            String notifications[] = {
187                    NOTIF_PINGED_NAME 
188            };
189            String NOTIF_INFO_DESCRIPTION = 
190                "notification set for " + getClass().getName();
191            MBeanNotificationInfo nI[] = new MBeanNotificationInfo[] {
192                    new MBeanNotificationInfo(
193                            notifications,
194                            Notification.class.getName(),
195                            NOTIF_INFO_DESCRIPTION )
196            };
197            
198            // now, MBeanInfo for this level of class hierarchy
199            MBeanInfo mBeanInfo = new MBeanInfo( getClass().getName(), 
200                    DESCRIPTION,
201                    aI, cI, oI, nI );
202            
203            return mBeanInfo;
204            
205        }
206    
207        
208        /**
209         * this is a merge of the agent, this class, and this class's superclass.
210         * @return MBean information for ping agent including JademxAgent
211         */
212        public MBeanInfo getMBeanInfo() {
213            return mBeanInfoMerged;
214        }
215        
216        
217        
218        /* (non-Javadoc)
219         * @see jade.core.Agent#setup()
220         */
221        protected void setup() {
222            // make sure set up as a JademxAgent
223            super.setup();
224            // get content manager set up
225            contentManager = getContentManager();
226            contentManager.registerLanguage( codec );
227            contentManager.registerOntology( ontology );
228            // add the PingResponderBehaviour
229            addBehaviour( 
230                    new PingResponder( 
231                        this, 
232                        MessageTemplate.MatchPerformative( ACLMessage.REQUEST ) ) );
233        }
234        // ACTUAL ATTRIBUTE AND OPERATION
235        
236        /** count pings for benefit of reply-with in messages */
237        private int pingCount = 0;
238        /**
239         * perform ping operation
240         * @param pingeeAgentFullName full JADE name of agent to ping
241         * @return current response
242         */
243        public String ping( String pingeeAgentFullName ) throws Exception {
244            
245            // get the ping message ready
246            
247            ACLMessage request = new ACLMessage( ACLMessage.REQUEST );
248            AID receiver = new AID( pingeeAgentFullName, AID.ISGUID );
249            request.addReceiver( receiver );
250            request.setConversationId( "ping-conv-" + 
251                    Long.toString( System.currentTimeMillis() ) +
252                    "-" + 
253                    Long.toString( this.hashCode() ) );
254            request.setLanguage( codec.getName() );
255            request.setOntology( ontology.getName() );
256            request.setProtocol( FIPANames.InteractionProtocol.FIPA_REQUEST );
257            request.setReplyWith( "ping" + Integer.toString( ++pingCount ) );
258            request.setSender( getAID() );
259            
260            Ping ping = new Ping();
261            Action pingAction = new Action();
262            pingAction.setActor( receiver );
263            pingAction.setAction( ping );
264            
265            contentManager.fillContent( request, pingAction );
266            
267            logger.log( Logger.FINE, "request: "+request);
268            
269            // start behaviour to perform ping and wait for response.
270            // OK to use wait() because this is a thread belonging to the 
271            // MBeanServer and the behaviour runs in a different thread.
272    
273            String    response = null;
274            Exception exception = null;
275            PingResponse pingResponse = new PingResponse();
276            synchronized( pingResponse ) {
277                // enqueue behaviour to perform ping
278                addBehaviour( new PingInitiator( this, request, pingResponse ) );
279                // block until have response
280                while ( ( null == ( exception = pingResponse.getException() ) ) && 
281                        ( null == ( response  = pingResponse.getResponse()  ) ) ) {
282                    pingResponse.wait();
283                }
284            }
285    
286            // if got an exception performing ping, re-throw it
287    
288            if ( null != exception ) {
289                throw exception;
290            }
291            
292            // return the response
293            
294            return response;
295        }
296        
297        /**
298         * set the response
299         * @param response response to set
300         */
301        public void setResponse( String response ) {
302            synchronized ( this.response ) {
303                this.response = response;
304            }
305        }
306        
307        /**
308         * get response
309         * @return response
310         */
311        public String getResponse() {
312            synchronized ( this.response ) {
313                return response;
314            }
315        }
316        
317        // MBEAN INTERFACE TO ATTRIBUTE AND OPERATION
318        
319        /**
320         * get an attribute value
321         * @param attribute name of attribute to get
322         * @return attribute value
323         * @throws AttributeNotFoundException no such attribute
324         * @throws MBeanException exception from getter
325         * @throws ReflectionException exception invoking getter
326         */
327        public Object getAttribute( String attribute ) 
328          throws AttributeNotFoundException,
329                 MBeanException, 
330                 ReflectionException {
331            Object o;
332            // attributes known about directly at this level        
333            if ( ATTR_RESPONSE_NAME.equals( attribute ) ) {
334                o = getResponse();
335            }
336            // attributes known by parent class
337            else {
338                o = super.getAttribute( attribute );            
339            }
340            return o;
341        }
342    
343        /**
344         * @param attribute
345         * @throws AttributeNotFoundException no such attribte
346         * @throws InvalidAttributeValueException bad attribute value
347         * @throws MBeanException exception from setter
348         * @throws ReflectionException exception invoking setter
349         */
350        public void setAttribute( Attribute attribute )
351          throws AttributeNotFoundException,
352                 InvalidAttributeValueException,
353                 MBeanException,
354                 ReflectionException {
355     
356            String attributeName = attribute.getName();
357            Object v = attribute.getValue();
358            // attributes known about directly at this level
359            if ( ATTR_RESPONSE_NAME.equals( attributeName ) ) {
360                String s;
361                try {
362                    s = (String) v;
363                }
364                catch ( ClassCastException cce ){
365                    throw new InvalidAttributeValueException( "for attribute "+
366                            " need "+String.class.getName()+" got "+
367                            v.getClass().getName() );
368                }
369                try {
370                    setResponse( s );
371                    // invoking directly, not indirectly, 
372                    // so can't throw ReflectionException
373                }
374                catch ( Exception e ) {
375                    throw new MBeanException( e,
376                            "exception setting "+ATTR_RESPONSE_NAME+" attribute" );
377                }
378            }
379            // attributes known by parent class
380            else {
381                super.setAttribute( attribute );
382            }
383        }
384    
385    
386        /**
387         * invoke an action
388         * @param actionName name of action to invoke
389         * @param params action parameters
390         * @param signature action signature
391         * @return object result
392         * @exception MBeanException wrap action exception
393         * @exception ReflectionException wrap action invocation exception
394         */
395        public Object invoke(String actionName, Object params[], String signature[])
396              throws MBeanException, ReflectionException {
397            Object o = null;
398            if ( OPER_PING_NAME.equals( actionName ) && 
399                 MBeanUtil.signaturesEqual( OPER_PING_SIGNATURE, signature ) ) {
400                try {
401                    o = ping( (String)params[0] );
402                }
403                catch ( Exception e ) {
404                    throw new MBeanException( e, 
405                            "exception invoking "+OPER_PING_NAME+" operation");
406                }
407            }
408            else {
409                super.invoke( actionName, params, signature );
410            }
411            return o;
412        }
413        
414        /** name of ping schema */
415        public static final String PING = "ping";
416    
417        /** the ping ontology */
418        private static class PingOntology extends Ontology {
419            /** constant identifying the ontology name **/
420            public static final String NAME = "ping";
421            /** the singleton instance */
422            private static Ontology theInstance = new PingOntology();
423            /** return singleton instance */
424            public static Ontology getInstance() {
425                return theInstance;
426            }
427            /** make a ping ontology */
428            private PingOntology() {
429                super( NAME, BasicOntology.getInstance());
430                try {
431                    ConceptSchema pingSchema = new ConceptSchema( PING );
432                    add( pingSchema, Ping.class );
433                } 
434                catch ( OntologyException oe ) {
435                    oe.printStackTrace();
436                    throw new RuntimeException( "Unrecoverable OntologyException",
437                            oe );
438                }
439            }
440        }
441        
442        
443        /** 
444         * response to ping.
445         * meant as wrapper to be handled with <code>Object.wait()</code> and
446         * <code>Object.notify()</code>.
447         */
448        private class PingResponse {
449            /** hold the response */
450            private String response = null;
451            /** hold any Exception generated trying to get response */
452            private Exception exception = null;
453            /**
454             * get the ping response
455             * @return Returns the response.
456             */
457            public String getResponse() {
458                return response;
459            }
460            /**
461             * set the ping response
462             * @param response The response to set.
463             */
464            public void setResponse(String response) {
465                this.response = response;
466            }
467            /**
468             * get any exception set
469             * @return Returns the exception
470             */
471            public Exception  getException() {
472                return exception;
473            }
474            /**
475             * set a exception received trying to get response
476             * @param exception The exception to set.
477             */
478            public void setException(Exception exception) {
479                this.exception = exception;
480            }
481        }
482        
483        
484        /** 
485         * behaviour to send a ping and get response
486         */
487        private class PingInitiator extends SimpleAchieveREInitiator {
488            
489            /** ping response object */
490            private PingResponse pingResponse;
491    
492            /**
493             * @param a my agent
494             * @param msg message to send
495             * @param pingResponse PingResponse object to set and notify()
496             */
497            public PingInitiator( 
498                    Agent a, 
499                    ACLMessage msg, 
500                    PingResponse pingResponse ) {
501                super( a, msg );
502                this.pingResponse = pingResponse;
503            }
504            
505            /* (non-Javadoc)
506             * @see jade.proto.SimpleAchieveREInitiator#handleInform(
507             * jade.lang.acl.ACLMessage)
508             */
509            protected void handleInform( ACLMessage msg ) {
510                logger.log( Logger.FINE, "ping response:"+msg);
511                String response = null;
512                synchronized( pingResponse ) {
513                    // extract content
514                    Result result = null;
515                    try {
516                        result = (Result)contentManager.extractContent( msg );
517                        response = (String)result.getValue();
518                        // set the response in the response object
519                        pingResponse.setResponse( response );
520                    }
521                    catch ( Exception e ) {
522                        // let ping() know that encountered a problem
523                        pingResponse.setException( e );
524                    }
525                    catch ( Throwable t ) {
526                        // this way the MBeanServer won't wait unnecessarily
527                        pingResponse.setException( new RuntimeException(
528                                "throwable encountered performing ping", t ) );
529                    }
530                    // notify waiting thread that response has been received
531                    pingResponse.notify();
532                }
533            }
534        }
535        
536        
537        /** 
538         * behaviour to respond to a ping
539         */
540        private class PingResponder extends SimpleAchieveREResponder {
541            
542            /** my content manager */
543            ContentManager contentManager;
544            
545            /**
546             * @param a my agent
547             * @param mt message template to use
548             */
549            public PingResponder( JademxPingAgent a, MessageTemplate mt ) {
550                super(a, mt);
551                pingAgent = a;
552                contentManager = a.getContentManager();
553            }
554            /** my typed agent */
555            private JademxPingAgent pingAgent;
556            
557            /* (non-Javadoc)
558             * @see jade.proto.SimpleAchieveREResponder#prepareResponse(
559             * jade.lang.acl.ACLMessage)
560             */
561            protected ACLMessage prepareResponse( ACLMessage request )
562                    throws NotUnderstoodException, RefuseException {
563                
564                logger.log( Logger.FINE, "ping request: "+request);
565    
566                // extract content
567                Action action;
568                try {
569                    action = (Action)contentManager.extractContent( request );
570                    
571                }
572                catch ( Exception e ) {
573                    e.printStackTrace();
574                    throw new NotUnderstoodException( 
575                            "didn't understand "+request+":"+ e );
576                } 
577                
578                // create reply
579                ACLMessage reply = request.createReply();
580                reply.setPerformative( ACLMessage.INFORM );
581                Result result = new Result( action, pingAgent.getResponse() );
582                try {
583                    contentManager.fillContent( reply, result );
584                }
585                catch ( Exception e ) {
586                    throw new RefuseException( 
587                            "can't create reply to "+request+":"+ e );
588                }             
589                
590                // notify any JMX listeners that we were pinged
591                String pinger = request.getSender().getName();
592                pingAgent.notifyListeners( 
593                        NOTIF_PINGED_NAME, 
594                        "this agent was pinged by "+pinger, 
595                        pinger );
596    
597                // send back reply
598                logger.log( Logger.FINE, "reply: "+reply);
599                return reply;
600            }
601        }
602        
603    }