MBeanCapabilityBuilder.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.wsdm.capabilities;
022 
023 import javassist.CannotCompileException;
024 import javassist.ClassClassPath;
025 import javassist.ClassPool;
026 import javassist.CtClass;
027 import javassist.CtField;
028 import javassist.CtMethod;
029 import javassist.CtNewMethod;
030 
031 import javax.management.MBeanAttributeInfo;
032 import javax.management.MBeanOperationInfo;
033 import javax.management.MBeanParameterInfo;
034 import javax.management.ObjectName;
035 import javax.xml.namespace.QName;
036 
037 import org.apache.muse.core.Environment;
038 import org.apache.qpid.management.Messages;
039 import org.apache.qpid.management.Names;
040 import org.apache.qpid.management.wsdm.common.EntityInstanceNotFoundFault;
041 import org.apache.qpid.management.wsdm.common.MethodInvocationFault;
042 import org.apache.qpid.management.wsdm.common.NoSuchAttributeFault;
043 import org.apache.qpid.management.wsdm.common.QManFault;
044 import org.apache.qpid.transport.util.Logger;
045 
046 /**
047  * Builder for capability class that will implements the interface 
048  * and the behaviour of the underlying JMX Entity.
049  * The product of this builder (capability class) will be used for create a new instance
050  * of the corresponding capability. It will be the "adapter" between WS-Resource and 
051  * JMX MBean.
052  
053  @author Andrea Gazzarini
054  */
055 public class MBeanCapabilityBuilder implements IArtifactBuilder{
056     
057   private final static String GET_PROPERTY_NAMES_METHOD_COMMON_PART = "public QName[] getPropertyNames() { return ";
058   private final static String GET_PROPERTY_NAMES_METHOD_WITH_ARRAY = GET_PROPERTY_NAMES_METHOD_COMMON_PART+" PROPERTIES;}";
059   private final static String GET_PROPERTY_NAMES_METHOD_WITH_EMPTY_ARRAY = GET_PROPERTY_NAMES_METHOD_COMMON_PART+" new QName[0];}";
060   private final static Logger LOGGER = Logger.get(MBeanCapabilityBuilder.class);
061   
062   /**
063    * Handler interface definining operation needed to be 
064    * peformed (by a concrete implementor) when the "endAttributes" 
065    * director callback happens.
066    
067    @author Andrea Gazzarini
068    */
069   interface EndAttributesHandler {
070     
071     /**
072      * Concrete implementor must define in this method what
073      * needs to be done when the corresponding director callback
074      * happens (@see {@link MBeanCapabilityBuilder#endAttributes()}
075      
076      @throws BuilderException when a failure is raised inside the concrete implementation.
077      */
078     void endAttributes() throws BuilderException;
079   };
080   
081   /**
082    * This is the concrete implementation of the internal interface EndAttributesHandler
083    * that is activated when this builder detects the presence of at least one property on the 
084    * capability class.
085    */
086   final EndAttributesHandler _atLeastThereIsOneProperty = new EndAttributesHandler() {
087 
088     /**
089      * Creates the QName array instance member and the corresponding 
090      * accessor getPropertyNames().
091      
092      @throws BuilderException when the member above cannot be added to the capability class.
093      */
094     public void endAttributes() throws BuilderException
095     {
096       try 
097       {
098         _properties.deleteCharAt(_properties.length()-1);
099         _properties.append("};");
100 
101         CtField properties = CtField.make(_properties.toString(), _capabilityClassDefinition);
102         
103          _capabilityClassDefinition.addField(properties);
104         
105         CtMethod getPropertyNames = CtNewMethod.make(
106             GET_PROPERTY_NAMES_METHOD_WITH_ARRAY,
107             _capabilityClassDefinition);
108         _capabilityClassDefinition.addMethod(getPropertyNames);
109       catch(Exception exception
110       
111         throw new BuilderException(exception);
112       }      
113     }    
114   };
115   
116   /**
117    * This is the concrete implementation of the internal interface EndAttributesHandler
118    * that is activated when this builder detects that there are no properties defined for 
119    * the capability class.
120    */
121   final EndAttributesHandler _noPropertyHasBeenDefined= new EndAttributesHandler() 
122   {
123     /**
124      * Creates the getPropertyNames() that simply returns an empty QName array.
125      
126      @throws BuilderException when the member above cannot be added to the capability class.
127      */
128     public void endAttributes() throws BuilderException
129     {
130       try 
131       {
132         CtMethod getPropertyNames = CtNewMethod.make(
133             GET_PROPERTY_NAMES_METHOD_WITH_EMPTY_ARRAY,
134             _capabilityClassDefinition);
135         _capabilityClassDefinition.addMethod(getPropertyNames);
136       catch(Exception exception
137       
138         throw new BuilderException(exception);
139       }      
140     }    
141   };
142   
143   /**
144    * This is the active state for this builder when the requested class has never been
145    * built.
146    */
147    IArtifactBuilder _classNotAvailable = new IArtifactBuilder()
148   {
149 
150     /**
151      * Build process begins.
152      * The given object name is used to build a minimal definition of the product class.
153      
154      @param objectName the name of the JMX entity.
155      @throws BuilderException when the initial definiton of the capability cannot be created.
156      */
157     public void begin(ObjectName objectNamethrows BuilderException
158     {
159       String className = objectName.getKeyProperty(Names.CLASS);
160       ClassPool pool = ClassPool.getDefault();
161       pool.insertClassPath(new ClassClassPath(MBeanCapabilityBuilder.class));
162       pool.importPackage(QName.class.getPackage().getName());
163       pool.importPackage(ObjectName.class.getPackage().getName());
164       pool.importPackage(QManFault.class.getPackage().getName());    
165       pool.importPackage(Names.class.getPackage().getName());
166       pool.importPackage(Result.class.getPackage().getName());
167       pool.importPackage(NoSuchAttributeFault.class.getPackage().getName());
168       pool.importPackage(EntityInstanceNotFoundFault.class.getPackage().getName());
169       pool.importPackage(MethodInvocationFault.class.getPackage().getName());
170       
171       _capabilityClassDefinition = pool.makeClass("org.apache.qpid.management.wsdm.capabilities."+className);
172       try 
173       {
174         _capabilityClassDefinition.setSuperclass(pool.get(MBeanCapability.class.getName()));
175       catch(Exception exception
176       {
177         throw new BuilderException(exception);
178       
179     }
180 
181     public void endAttributes() throws BuilderException
182     {
183       _endAttributeHandler.endAttributes();
184     }
185 
186     @SuppressWarnings("unchecked")
187     public void endOperations() throws BuilderException
188     {
189       try 
190       {
191         _capabilityClass = _capabilityClassDefinition.toClass(
192             QManAdapterCapability.class.getClassLoader(),
193             QManAdapterCapability.class.getProtectionDomain());
194       catch (Exception exception
195       {
196         throw new BuilderException(exception);
197       }
198     }
199 
200     /**
201      * Director callback. 
202      * Attrbute metadata notification. With this callback the director informs this builder that the 
203      * currently processed MBean has an attribute with the given metadata.
204      * This builder uses this information in order to add a property and the corresponding accessors
205      * to the capability class that is going to be built.
206      
207      *  @throws BuilderException bytecode manipulation / creation failure.
208      */
209     public void onAttribute(MBeanAttributeInfo attributethrows BuilderException
210     {
211       String name = attribute.getName();
212       String type = attribute.getType();
213       
214       try 
215       {
216         type = Class.forName(type).getCanonicalName();
217         
218         addPropertyMemberInstance(type, name);
219 
220         String nameForAccessors = getNameForAccessors(name);
221         
222         if (attribute.isReadable()) 
223         {
224           String accessor = generateGetter(type, nameForAccessors,name);
225           CtMethod getter = CtNewMethod.make(accessor,_capabilityClassDefinition);
226           _capabilityClassDefinition.addMethod(getter);    
227           appendToPropertiesArray(name);
228 
229           LOGGER.debug(
230               Messages.QMAN_200043_GENERATED_ACCESSOR_METHOD,
231               _objectName,
232               accessor);
233         }
234         
235         if (attribute.isWritable()) 
236         {
237           String accessor = generateSetter(type, nameForAccessors,name);
238           CtMethod setter = CtNewMethod.make(accessor,_capabilityClassDefinition);
239           _capabilityClassDefinition.addMethod(setter);          
240 
241           LOGGER.debug(
242               Messages.QMAN_200043_GENERATED_ACCESSOR_METHOD,
243               _objectName,
244               accessor);
245         }    
246       catch(Exception exception)
247       {
248         throw new BuilderException(exception);
249       }
250     }
251 
252     public void onOperation(MBeanOperationInfo operationthrows BuilderException
253     {
254       StringBuilder method = new StringBuilder();
255       try 
256       {
257         method
258           .append("public Result ")
259           .append(operation.getName())
260           .append("( ");
261         
262         for (MBeanParameterInfo parameter: operation.getSignature())
263         {
264           method
265             .append(Class.forName(parameter.getType()).getCanonicalName())
266             .append(' ')
267             .append(parameter.getName())
268             .append(',');
269         }
270         
271         method.deleteCharAt(method.length()-1);
272         method.append(") throws EntityInstanceNotFoundFault, MethodInvocationFault,QManFault { return invoke(")
273           .append("\"").append(operation.getName()).append("\"")
274           .append(", new Object[]{ ");
275         
276         for (MBeanParameterInfo parameter: operation.getSignature())
277         {
278           method.append(parameter.getName())
279             .append(',');
280         }
281         
282         method.deleteCharAt(method.length()-1);      
283         method.append("}, new String[]{ ");
284         
285         for (MBeanParameterInfo parameter: operation.getSignature())
286         {
287           method
288             .append("\"")
289             .append(parameter.getType())
290             .append("\",");
291         }
292         method.deleteCharAt(method.length()-1);      
293         method.append("}); }");
294         
295         String methodAsString = method.toString();
296         methodAsString = methodAsString.replace("new Object[]{}","null");
297         methodAsString = methodAsString.replace("new String[]{}","null");
298         
299         CtMethod definition = CtNewMethod.make(methodAsString,_capabilityClassDefinition);
300         _capabilityClassDefinition.addMethod(definition);      
301       catch(Exception exception)
302       {
303         throw new BuilderException(exception);
304       finally {
305         if (LOGGER.isDebugEnabled())
306         {
307           LOGGER.debug(
308               Messages.QMAN_200044_GENERATED_METHOD, 
309               _objectName,
310               method.toString());
311         }
312       }
313     }
314     
315     public void setEnvironment(Environment environment)
316     {
317       // Nothing to do here...
318     }
319   };
320   
321   StringBuilder _properties = new StringBuilder("private static final QName[] PROPERTIES = new QName[]{ ");
322   private Class<MBeanCapability> _capabilityClass;
323   CtClass _capabilityClassDefinition;
324   EndAttributesHandler _endAttributeHandler = _noPropertyHasBeenDefined;
325   
326   private ObjectName _objectName;
327   
328   IArtifactBuilder _state;
329   
330   /**
331    * Director callback. 
332    * Attrbute metadata notification. With this callback the director informs this builder that the 
333    * currently processed MBean has an attribute with the given metadata.
334    * This builder uses this information in order to add a property and the corresponding accessors
335    * to the capability class that is going to be built.
336    
337    *  @throws BuilderException bytecode manipulation / creation failure.
338    */
339   public void onAttribute(MBeanAttributeInfo attributethrows BuilderException 
340   {
341     _state.onAttribute(attribute);
342   }
343 
344   /**
345    * First callback : this method is called at the begin of the director process.
346    * Contains builder initialization code.
347    
348    @param objectName the name of the target JMX entity of this capability.
349    @throws BuilderException when the initialization fails.
350    */
351   @SuppressWarnings("unchecked")
352   public void begin(ObjectName objectNamethrows BuilderException 
353   {
354     try
355     {
356       this._objectName = objectName;
357       String className = objectName.getKeyProperty(Names.CLASS);
358       _capabilityClass = (Class<MBeanCapability>Class.forName("org.apache.qpid.management.wsdm.capabilities."+className);
359       _state = new DummyCapabilityBuilder();
360     catch (ClassNotFoundException exception)
361     {
362       _state = _classNotAvailable;
363     }
364   
365     _state.begin(objectName);
366   }
367   
368   /**
369    * Director callback. 
370    * Operation metadata notification. With this callback the director informs this builder that the 
371    * currently processed MBean has an operation with the given metadata.
372    * This builder uses this information in order to add a method to the capability class that is 
373    * going to be built.
374    
375    * For example, let's suppose that an operation like that is detected on the MBean :
376    
377    * public void purge(int request)
378    
379    * then the capability will be enrichied with the following method :
380    
381    * public void purge(int request) throws QManFault {
382    *   invoke(
383    *     "purge",
384    *     new Object[]{request},
385    *     new String[]{int.class.getName()});
386    * }
387    
388    *  @throws BuilderException bytecode manipulation / creation failure.
389    */
390   public void onOperation(MBeanOperationInfo operation)  throws BuilderException
391   {
392     _state.onOperation(operation);
393   }
394 
395   /**
396    * Returns the capability class (the product of this builder). 
397    
398    @return the capability class (the product of this builder).
399    */
400   Class<MBeanCapability> getCapabilityClass() 
401   {
402     return _capabilityClass;
403   }
404 
405   /**
406    * Determines what needs to be done when all attributes 
407    * metadata has been notified to this builder.
408    * Capability class must have an array member with all defined 
409    * properties and a getter method that returns it.
410    * In this method those two members are declared (obviously only 
411    * if this capability has at least one property).
412    
413    @throws BuilderException when something fails during this phase.
414    */
415   public void endAttributes() throws BuilderException
416   {
417     _state.endAttributes();
418   }
419 
420   /**
421    * Director callback. 
422    * This method is notified when all operations metadata has been 
423    * notified to this builder.
424    * This is the place where the capability class is created, defined and loaded by the JVM.
425    
426    *  @throws BuilderException issues on this method are basically class loading related.
427    */
428   @SuppressWarnings("unchecked")
429   public void endOperations() throws BuilderException
430   {
431     _state.endOperations();
432   }
433 
434   /**
435    * Injects the module environment on this builder.
436    
437    @param environment the module environment.
438    */
439   public void setEnvironment(Environment environment
440   {
441     // Nothing to do here...
442   }
443   
444   /**
445    * Generates the get accessor method for the given property.
446    *  
447    @param type the type of the property.
448    @param name the name of the property with the first letter capitalized.
449    @param plainName the plain name of the property.
450    @return the getter method (as a string).
451    */
452   String generateGetter(String type, String name,String plainName
453   {
454     return new StringBuilder()
455       .append("public ")
456       .append(type)
457       .append(' ')
458       .append("get")
459       .append(name)
460       .append("() throws NoSuchAttributeFault,EntityInstanceNotFoundFault,QManFault { return (")
461       .append(type)
462       .append(") getAttribute(\"")
463       .append(plainName)
464       .append("\"); }")
465       .toString();
466   }
467 
468   /**
469    * Generates the set accessor method for the given property.
470    *  
471    @param type the type of the property.
472    @param name the name of the property with the first letter capitalized.
473    @param plainName the plain name of the property.
474    @return the setter method (as a string).
475    */
476   String generateSetter(String type, String name, String plainName
477   {
478     return new StringBuilder()
479       .append("public void ")
480       .append("set")
481       .append(name)
482       .append("(")
483       .append(type)
484       .append(" newValue) throws NoSuchAttributeFault,EntityInstanceNotFoundFault,QManFault {")
485       .append(" setAttribute(\"")
486       .append(plainName)
487       .append("\", newValue); }")
488       .toString();
489   }
490   
491   /**
492    * Appends the given attribute name to the properties array declared as an
493    * instance member of the capability class.
494    
495    @param attributeName the name of the attribute.
496    */
497   private void appendToPropertiesArray(String attributeName)
498   {
499     _properties.append("new QName(Names.NAMESPACE_URI, \"")
500     .append(attributeName)
501     .append("\", Names.PREFIX),");    
502 
503     _endAttributeHandler = _atLeastThereIsOneProperty;
504   }
505   
506   /** 
507    * Adds a new property member instance to the capability class.
508    
509    @param type the type of the property.
510    @param name the name of the property.
511    @throws CannotCompileException  when the property cannot be added.
512    */
513   private void addPropertyMemberInstance(String type, String namethrows CannotCompileException
514   {
515     StringBuilder buffer = new StringBuilder()
516       .append("private ")
517       .append(type)
518       .append(' ')
519       .append(name)
520       .append(';');
521 
522     CtField field= CtField.make(buffer.toString(),_capabilityClassDefinition);
523     _capabilityClassDefinition.addField(field);    
524   }  
525   
526   /**
527    * Returns a name that will be used in accessor methods.
528    * That name will differ from the given one because the first letter will be capitalized.
529    * For example, if the given name is "name" the return value will be "Name".
530    
531    @param name the plain name of the attribute.
532    @return a capitalized version of the given name to be used in accessors.
533    */
534   String getNameForAccessors(String name)
535   {
536     return 
537       Character.toUpperCase(name.charAt(0)) 
538       name.substring(1);
539   }
540   
541 }