QpidEvent.java
001 /*
002  *
003  * Licensed to the Apache Software Foundation (ASF) under one
004  * or more contributor license agreements.  See the NOTICE file
005  * distributed with this work for additional information
006  * regarding copyright ownership.  The ASF licenses this file
007  * to you under the Apache License, Version 2.0 (the
008  * "License"); you may not use this file except in compliance
009  * with the License.  You may obtain a copy of the License at
010  *
011  *   http://www.apache.org/licenses/LICENSE-2.0
012  *
013  * Unless required by applicable law or agreed to in writing,
014  * software distributed under the License is distributed on an
015  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016  * KIND, either express or implied.  See the License for the
017  * specific language governing permissions and limitations
018  * under the License.
019  *
020  */
021 package org.apache.qpid.management.domain.model;
022 
023 import java.nio.ByteBuffer;
024 import java.util.ArrayList;
025 import java.util.Date;
026 import java.util.HashMap;
027 import java.util.LinkedList;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.UUID;
031 
032 import javax.management.Attribute;
033 import javax.management.AttributeList;
034 import javax.management.AttributeNotFoundException;
035 import javax.management.InvalidAttributeValueException;
036 import javax.management.MBeanAttributeInfo;
037 import javax.management.MBeanException;
038 import javax.management.MBeanInfo;
039 import javax.management.ObjectName;
040 import javax.management.ReflectionException;
041 import javax.management.RuntimeOperationsException;
042 
043 import org.apache.qpid.management.Messages;
044 import org.apache.qpid.management.Names;
045 import org.apache.qpid.management.domain.model.type.Binary;
046 import org.apache.qpid.management.jmx.EntityLifecycleNotification;
047 import org.apache.qpid.transport.codec.BBDecoder;
048 
049 /**
050  * Qpid event definition.
051  */
052 class QpidEvent extends QpidEntity implements QpidEventMBean
053 {        
054     
055     /**
056      * State interface for this event definition.
057      * Each state is responsible to handle the injection of the data and / or schema. 
058      */
059     interface State 
060     {    
061         /**
062          * Adds the given data for the object instance associated to the given object identifier.
063          
064          @param rawData the raw configuration data.
065          */
066         void addNewEventData (byte[] rawData, long currentTimestamp, int severity);
067 
068         /**
069          * Inject the schema into this class definition.
070          
071          @param propertyDefinitions
072          @param statisticDefinitions
073          @param methodDefinitions
074          @throws UnableToBuildFeatureException when it's not possibile to parse schema and build the class definition.
075          */
076         public void setSchema (List<Map<String, Object>> agumentDefinitionsthrows UnableToBuildFeatureException;
077     };
078 
079   
080     /**
081      * This is the initial state of every qpid class. 
082      * The class definition instance is created but its schema has not been injected. 
083      * Incoming configuration & instrumentation data will be stored in raw format because we don't know how to 
084      * parse it until the schema arrives.
085      * In addition, this state is responsible (when data arrives) to request its schema.
086      */
087     final State _schemaNotRequested = new State() {
088 
089         /**
090          * Stores the incoming data in raw format and request the schema for this class.
091          * After that a transition to the next state is made.
092          
093          @param objectId the object instance identifier.
094          @param rawData incoming configuration data.
095          */
096         public synchronized void addNewEventData (byte[] rawData, long currentTimestamp, int severity)
097         {
098             try
099             {
100                 requestSchema();  
101                 _state = _schemaRequestedButNotYetInjected;
102             catch (Exception exception)
103             {
104                 _logger.error(
105                     exception,
106                         Messages.QMAN_100015_UNABLE_TO_SEND_SCHEMA_REQUEST, 
107                         _parent.getName(),
108                         _name);
109             finally {
110                 createEventInstance(rawData,currentTimestamp,severity);
111             }
112         }
113 
114         /**
115          * This method only throws an illegal state exception because when a schema arrives 
116          * this state is no longer valid.
117          */
118         public  void  setSchema (List<Map<String, Object>> agumentDefinitionsthrows UnableToBuildFeatureException
119         {
120             throw new IllegalStateException("When a schema arrives it's not possible for this event to be in this state.");
121         }
122     };
123     
124     /**
125      * This is the first state of this class definition : the schema is not yet injected so each injection of object data will be 
126      * retained in raw format.
127      */
128     final State _schemaRequestedButNotYetInjected = new State()
129     {
130         /**
131          * Stores the incoming data in raw format and request the schema for this class.
132          * After that a transition to the next state is made.
133          
134          @param objectId the object instance identifier.
135          @param rawData incoming configuration data.
136          */
137         public synchronized void addNewEventData (byte[] rawData,long currentTimestamp, int severity)
138         {
139           createEventInstance(rawData,currentTimestamp, severity);
140         }
141 
142         /**
143          * When a schema is injected into this defintiion the following should happen :
144          * 1) the incoming schema is parsed and the class definition is built;
145          * 2) the retained raw data is converted into object instance(s)
146          * 3) the internal state of this class changes;
147          
148          * If someting is wrong during that process the schema is not built and the state don't change.
149          */
150         public synchronized void setSchema (List<Map<String, Object>> argumentDefinitionsthrows UnableToBuildFeatureException
151         {
152           MBeanAttributeInfo [] attributesMetadata = new MBeanAttributeInfo[argumentDefinitions.size()+2];
153                 
154           buildArguments(argumentDefinitions, attributesMetadata);
155 
156           _metadata = new MBeanInfo(_name,_name,attributesMetadata,null,null,null);
157             
158             // Converting stored object instances into JMX MBean and removing raw instance data.
159             for (QManManagedEvent instance : _eventInstances)
160             {                    
161               updateEventInstanceWithData(instance);
162                 registerEventInstance(instance,_parent.getOwnerId(),_parent.getName(),_name);
163             }
164             _state = _schemaInjected;
165             
166             EntityLifecycleNotification notification = new EntityLifecycleNotification(
167                    EntityLifecycleNotification.SCHEMA_INJECTED_NOTIFICATION_TYPE,
168                    _parent.getName()
169                    _name, 
170                    Names.EVENT,
171                    _objectName);
172                
173                sendNotification(notification);
174         }
175     };
176     
177     /**
178      * After a schema is built into this definition this is the current state of the class.
179      */
180     final State _schemaInjected = new State()
181     {
182         /**
183          * Updates the configuration state of the object instance associates with the given object identifier.
184          
185          @param objectId the object identifier.
186          @param rawData the configuration data (raw format).
187          */
188         public void addNewEventData (byte[] rawData,long currentTimestamp, int severity)
189         {
190             QManManagedEvent instance = createEventInstance(rawData,currentTimestamp, severity);            
191             updateEventInstanceWithData(instance);
192             registerEventInstance(instance,_parent.getOwnerId(),_parent.getName(),_name);
193         }
194         
195         /**
196          * Never called when the class definition has this state.
197          */
198         public  void  setSchema (List<Map<String, Object>> agumentDefinitionsthrows UnableToBuildFeatureException
199         {
200            // N.A. : Schema is already injected.
201         }
202     };
203     
204     /**
205      * MBean used for representing remote broker object instances.
206      * This is the core component of the QMan domain model
207      
208      @author Andrea Gazzarini
209      */
210     class QManManagedEvent extends QManManagedEntity
211     {        
212 
213 
214         // Arrays used for storing raw data before this mbean is registered to mbean server.
215         final byte[] _rawEventData;
216         final long _timestamp;
217         final int _severity;
218         
219         /**
220          * Builds a new managed object with the given object identifier.
221          
222          @param objectId the object identifier.
223          */
224         private QManManagedEvent(byte [] data, long timestamp, int severity)
225         {
226           this._rawEventData = data;
227           this._timestamp = timestamp;
228           this._severity = severity;
229           _attributes.put(SEVERITY_ATTR_NAME, _severity);
230           _attributes.put(TIMESTAMP_ATTR_NAME, new Date(_timestamp));
231         }
232         
233         /**
234          * Returns the value of the given attribute.s
235          
236          @throws AttributeNotFoundException when no attribute is found with the given name.
237          */
238         public Object getAttribute (String attributeNamethrows AttributeNotFoundException, MBeanException, ReflectionException
239         {
240             if (attributeName == null
241             {
242                 throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name must not be null."));
243             }
244             
245             if (_arguments.containsKey(attributeName|| SEVERITY_ATTR_NAME.equals(attributeName|| TIMESTAMP_ATTR_NAME.equals(attributeName))
246             {
247                 return _attributes.get(attributeName);  
248             else 
249             {
250                 throw new AttributeNotFoundException(attributeName);
251             }        
252         }
253 
254         /**
255          * Executes an operation on this object instance.
256          
257          @param actionName the name of the method.
258          @param params the method parameters 
259          @param signature the method signature.
260          */
261         public Object invoke (String actionName, Object[] params, String[] signaturethrows MBeanException,ReflectionException
262         {
263            throw new ReflectionException(new NoSuchMethodException(actionName));
264         }
265 
266         /**
267          * Sets the value of the given attribute on this object instance.
268          
269          @param attribute contains the new value of the attribute.
270          @throws AttributeNotFoundException when the given attribute is not found on this object instance.
271          @throws InvalidAttributeValueException when the given value is violating one attribute invariant.
272          */
273         public void setAttribute (Attribute attributethrows AttributeNotFoundException,
274                 InvalidAttributeValueException, MBeanException, ReflectionException
275         {
276             throw new ReflectionException(new NoSuchMethodException());
277         }
278 
279         /**
280          * Sets the values of several attributes of this MBean.
281          *
282          @param attributes a list of attributes: The identification of the attributes to be set and the values they are to be set to.
283          @return  The list of attributes that were set, with their new values.
284          */
285         public AttributeList setAttributes (AttributeList attributes)
286         {
287             throw new RuntimeException();
288         }
289     }
290 
291     final static String SEVERITY_ATTR_NAME = "Severity";
292     final static String TIMESTAMP_ATTR_NAME = "Date";
293     
294     private List<QpidProperty> _schemaOrderedArguments = new ArrayList<QpidProperty>();
295     
296     Map<String, QpidProperty> _arguments  = new HashMap<String, QpidProperty>();     
297     List<QManManagedEvent> _eventInstances = new LinkedList<QManManagedEvent>();
298     State _state = _schemaNotRequested;;
299         
300     /**
301      * Builds a new class with the given name and package as parent.
302      
303      @param className the name of the class.
304      @param hash the class schema hash.
305      @param parentPackage the parent of this class.
306      */
307     QpidEvent(String eventClassName, Binary hash, QpidPackage parentPackage)
308     {
309       super(eventClassName,hash,parentPackage,Names.EVENT);
310     }
311     
312     /**
313      * Adds the configuration data for the object instance associated to the given object identifier.
314      
315      @param objectId the object identifier.
316      @param rawData the raw configuration data.
317      */
318     void addEventData (byte[] rawData, long currentTimestamp, int severity)
319     {
320         _logger.debug(
321             Messages.QMAN_200021_INCOMING_EVENT_DATA,
322                 _parent.getOwnerId(),
323                 _parent.getName(),
324                 _name);        
325         _state.addNewEventData(rawData, currentTimestamp, severity);
326     }
327     
328     /**
329      * Sets the schema for this class definition. 
330      * A schema is basically a metadata description of all properties, statistics, methods and events of this class.
331      
332      @param propertyDefinitions properties metadata.
333      @param statisticDefinitions statistics metadata.
334      @param methodDefinitions methods metadata.
335      @throws UnableToBuildFeatureException when some error occurs while parsing the incoming schema.
336      */
337      void setSchema (List<Map<String, Object>> argumentDefinitionsthrows UnableToBuildFeatureException
338     {
339          _logger.info(Messages.QMAN_000010_INCOMING_SCHEMA,_parent.getOwnerId(),_parent.getName(),_name);
340         _state.setSchema(argumentDefinitions);
341     }    
342 
343     /**
344      * Internal method used for building attributes definitions.
345      
346      @param props the map contained in the properties schema.
347      @param stats the map contained in the statistics schema.
348      @param attributes the management metadata for attributes.
349      @throws UnableToBuildFeatureException  when it's not possibile to build one attribute definition.
350      */
351     void buildArguments (
352             List<Map<String, Object>> arguments,MBeanAttributeInfo[] attributesthrows UnableToBuildFeatureException
353     {
354         int index = 0;
355         
356         for (Map<String, Object> argumentDefinition : arguments)
357         {
358           // Force metadata attributes. It is needed because arguments are "similar" to properties but they 
359           // aren't properties and then they haven't optional, index and access metadata attributes 
360           // (mandatory for build a property definition).
361           argumentDefinition.put(QpidFeatureBuilder.Attribute.optional.name(),0);
362           argumentDefinition.put(QpidFeatureBuilder.Attribute.index.name(),1);          
363           argumentDefinition.put(QpidFeatureBuilder.Attribute.access.name(),3);                    
364             
365           QpidFeatureBuilder builder = QpidFeatureBuilder.createPropertyBuilder(argumentDefinition);
366             builder.build();
367             
368             QpidProperty argument = (QpidPropertybuilder.getQpidFeature();           
369                         
370             _arguments.put(argument.getName(),argument);
371             _schemaOrderedArguments.add(argument);
372             attributes[index++]=(MBeanAttributeInfobuilder.getManagementFeature();
373                        
374             _logger.debug(
375                     Messages.QMAN_200019_EVENT_ARGUMENT_DEFINITION_HAS_BEEN_BUILT,
376                     _parent.getName(),
377                     _name,
378                     argument);
379         }
380 
381         attributes[index++new MBeanAttributeInfo(
382             SEVERITY_ATTR_NAME,
383               Integer.class.getName(),
384               Messages.EVENT_SEVERITY_ATTRIBUTE_DESCRIPTION,
385               true,
386               false,
387             false);
388         
389         attributes[index++new MBeanAttributeInfo(
390             TIMESTAMP_ATTR_NAME,
391               Date.class.getName(),
392               Messages.EVENT_TIMESTAMP_ATTRIBUTE_DESCRIPTION,
393               true,
394               false,
395             false);
396     }    
397     
398     /**
399      * Returns the object instance associated to the given identifier.
400      * Note that if the identifier is not associated to any obejct instance, a new one will be created.
401      
402      @param objectId the object identifier.
403      @param registration a flag indicating whenever the (new ) instance must be registered with MBean server.
404      @return the object instance associated to the given identifier.
405      */
406     QManManagedEvent createEventInstance(byte [] data, long timestamp, int severity
407     {
408         QManManagedEvent eventInstance = new QManManagedEvent(data, timestamp, severity);
409         _eventInstances.add(eventInstance);
410         return eventInstance;
411     }
412     
413     /**
414      * Updates the given obejct instance with the given incoming configuration data.
415      
416      @param instance the managed object instance.
417      @param rawData the incoming configuration data which contains new values for instance properties.
418      */
419     void updateEventInstanceWithData(QManManagedEvent instance)
420     {
421         BBDecoder decoder = new BBDecoder();
422         decoder.init(ByteBuffer.wrap(instance._rawEventData));
423 
424         for (QpidProperty property : _schemaOrderedArguments)
425         {                  
426             try {
427                 Object value = property.decodeValue(decoder);
428                 instance.createOrReplaceAttributeValue(property.getName(),value);             
429             catch(Exception ignore) {
430                 _logger.error(Messages.QMAN_100016_UNABLE_TO_DECODE_FEATURE_VALUE, _parent.getName(),_name,property.getName());
431             }
432         }
433     }
434        
435     @Override
436     public String toString ()
437     {
438         return new StringBuilder()
439             .append(_parent.getOwnerId())
440             .append("::")
441             .append(_parent.getName())
442             .append(".")
443             .append(_name)
444             .toString();
445     }
446 
447     /**
448      * Deregisters all the object instances and release all previously acquired resources.
449      */
450     void releaseResources ()
451     {
452       _eventInstances.clear();
453       JMX_SERVICE.unregisterEvents();
454       JMX_SERVICE.unregisterClassDefinitions();   
455         _service.close();
456     }
457 
458     /**
459      * Checks if this event definition contains event instance(s).
460      
461      @return true if there is one or more managed instances.
462      */
463   boolean hasNoInstances() 
464   {
465     return _eventInstances.isEmpty();
466   }
467   
468   /**
469    * Compose method used for registering an mbean (event) instance.
470    
471    @param instance the mbean event.
472    @param brokerId the broker identifier.
473    @param packageName the package name.
474    @param eventClassName the event class name.
475    */
476   private void registerEventInstance(
477       QManManagedEvent instance,
478       UUID brokerId, 
479       String packageName, 
480       String eventClassName
481   {
482     ObjectName objectName = JMX_SERVICE.registerEventInstance(instance,brokerId,packageName,eventClassName);
483     
484       EntityLifecycleNotification notification = new EntityLifecycleNotification(
485        EntityLifecycleNotification.INSTANCE_ADDED_NOTIFICATION_TYPE,
486        packageName,
487        eventClassName,
488        Names.EVENT,
489        objectName);
490    
491    sendNotification(notification);
492   }
493 }