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 objectName) throws 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 attribute) throws 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 operation) throws 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 attribute) throws 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 objectName) throws 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 name) throws 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 }
|