001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.modeler.modules;
019:
020: import org.apache.commons.logging.Log;
021: import org.apache.commons.logging.LogFactory;
022: import org.apache.commons.modeler.AttributeInfo;
023: import org.apache.commons.modeler.ManagedBean;
024: import org.apache.commons.modeler.OperationInfo;
025: import org.apache.commons.modeler.ParameterInfo;
026: import org.apache.commons.modeler.Registry;
027: import org.apache.commons.modeler.ConstructorInfo;
028:
029: import javax.management.ObjectName;
030:
031: import java.lang.reflect.Method;
032: import java.lang.reflect.Modifier;
033: import java.lang.reflect.Constructor;
034: import java.math.BigDecimal;
035: import java.math.BigInteger;
036: import java.util.ArrayList;
037: import java.util.Enumeration;
038: import java.util.Hashtable;
039: import java.util.List;
040:
041: public class MbeansDescriptorsIntrospectionSource extends ModelerSource {
042: private static Log log = LogFactory
043: .getLog(MbeansDescriptorsIntrospectionSource.class);
044:
045: Registry registry;
046: String location;
047: String type;
048: Object source;
049: List mbeans = new ArrayList();
050:
051: public void setRegistry(Registry reg) {
052: this .registry = reg;
053: }
054:
055: public void setLocation(String loc) {
056: this .location = loc;
057: }
058:
059: /** Used if a single component is loaded
060: *
061: * @param type
062: */
063: public void setType(String type) {
064: this .type = type;
065: }
066:
067: public void setSource(Object source) {
068: this .source = source;
069: }
070:
071: public List loadDescriptors(Registry registry, String location,
072: String type, Object source) throws Exception {
073: setRegistry(registry);
074: setLocation(location);
075: setType(type);
076: setSource(source);
077: execute();
078: return mbeans;
079: }
080:
081: public void execute() throws Exception {
082: if (registry == null)
083: registry = Registry.getRegistry();
084: try {
085: ManagedBean managed = createManagedBean(registry, null,
086: (Class) source, type);
087: if (managed == null)
088: return;
089: managed.setName(type);
090:
091: mbeans.add(managed);
092:
093: } catch (Exception ex) {
094: log.error("Error reading descriptors ", ex);
095: }
096: }
097:
098: // ------------ Implementation for non-declared introspection classes
099:
100: static Hashtable specialMethods = new Hashtable();
101: static {
102: specialMethods.put("preDeregister", "");
103: specialMethods.put("postDeregister", "");
104: }
105:
106: private static String strArray[] = new String[0];
107: private static ObjectName objNameArray[] = new ObjectName[0];
108: // createMBean == registerClass + registerMBean
109:
110: private static Class[] supportedTypes = new Class[] {
111: Boolean.class, Boolean.TYPE, Byte.class, Byte.TYPE,
112: Character.class, Character.TYPE, Short.class, Short.TYPE,
113: Integer.class, Integer.TYPE, Long.class, Long.TYPE,
114: Float.class, Float.TYPE, Double.class, Double.TYPE,
115: String.class, strArray.getClass(), BigDecimal.class,
116: BigInteger.class, ObjectName.class,
117: objNameArray.getClass(), java.io.File.class, };
118:
119: /**
120: * Check if this class is one of the supported types.
121: * If the class is supported, returns true. Otherwise,
122: * returns false.
123: * @param ret The class to check
124: * @return boolean True if class is supported
125: */
126: private boolean supportedType(Class ret) {
127: for (int i = 0; i < supportedTypes.length; i++) {
128: if (ret == supportedTypes[i]) {
129: return true;
130: }
131: }
132: if (isBeanCompatible(ret)) {
133: return true;
134: }
135: return false;
136: }
137:
138: /**
139: * Check if this class conforms to JavaBeans specifications.
140: * If the class is conformant, returns true.
141: *
142: * @param javaType The class to check
143: * @return boolean True if the class is compatible.
144: */
145: protected boolean isBeanCompatible(Class javaType) {
146: // Must be a non-primitive and non array
147: if (javaType.isArray() || javaType.isPrimitive()) {
148: return false;
149: }
150:
151: // Anything in the java or javax package that
152: // does not have a defined mapping is excluded.
153: if (javaType.getName().startsWith("java.")
154: || javaType.getName().startsWith("javax.")) {
155: return false;
156: }
157:
158: try {
159: javaType.getConstructor(new Class[] {});
160: } catch (java.lang.NoSuchMethodException e) {
161: return false;
162: }
163:
164: // Make sure superclass is compatible
165: Class super Class = javaType.getSuperclass();
166: if (super Class != null && super Class != java.lang.Object.class
167: && super Class != java.lang.Exception.class
168: && super Class != java.lang.Throwable.class) {
169: if (!isBeanCompatible(super Class)) {
170: return false;
171: }
172: }
173: return true;
174: }
175:
176: /**
177: * Process the methods and extract 'attributes', methods, etc
178: *
179: * @param realClass The class to process
180: * @param methods The methods to process
181: * @param attMap The attribute map (complete)
182: * @param getAttMap The readable attributess map
183: * @param setAttMap The settable attributes map
184: * @param invokeAttMap The invokable attributes map
185: */
186: private void initMethods(Class realClass, Method methods[],
187: Hashtable attMap, Hashtable getAttMap, Hashtable setAttMap,
188: Hashtable invokeAttMap) {
189: for (int j = 0; j < methods.length; ++j) {
190: String name = methods[j].getName();
191:
192: if (Modifier.isStatic(methods[j].getModifiers()))
193: continue;
194: if (!Modifier.isPublic(methods[j].getModifiers())) {
195: if (log.isDebugEnabled())
196: log.debug("Not public " + methods[j]);
197: continue;
198: }
199: if (methods[j].getDeclaringClass() == Object.class)
200: continue;
201: Class params[] = methods[j].getParameterTypes();
202:
203: if (name.startsWith("get") && params.length == 0) {
204: Class ret = methods[j].getReturnType();
205: if (!supportedType(ret)) {
206: if (log.isDebugEnabled())
207: log.debug("Unsupported type " + methods[j]);
208: continue;
209: }
210: name = unCapitalize(name.substring(3));
211:
212: getAttMap.put(name, methods[j]);
213: // just a marker, we don't use the value
214: attMap.put(name, methods[j]);
215: } else if (name.startsWith("is") && params.length == 0) {
216: Class ret = methods[j].getReturnType();
217: if (Boolean.TYPE != ret) {
218: if (log.isDebugEnabled())
219: log.debug("Unsupported type " + methods[j]
220: + " " + ret);
221: continue;
222: }
223: name = unCapitalize(name.substring(2));
224:
225: getAttMap.put(name, methods[j]);
226: // just a marker, we don't use the value
227: attMap.put(name, methods[j]);
228:
229: } else if (name.startsWith("set") && params.length == 1) {
230: if (!supportedType(params[0])) {
231: if (log.isDebugEnabled())
232: log.debug("Unsupported type " + methods[j]
233: + " " + params[0]);
234: continue;
235: }
236: name = unCapitalize(name.substring(3));
237: setAttMap.put(name, methods[j]);
238: attMap.put(name, methods[j]);
239: } else {
240: if (params.length == 0) {
241: if (specialMethods.get(methods[j].getName()) != null)
242: continue;
243: invokeAttMap.put(name, methods[j]);
244: } else {
245: boolean supported = true;
246: for (int i = 0; i < params.length; i++) {
247: if (!supportedType(params[i])) {
248: supported = false;
249: break;
250: }
251: }
252: if (supported)
253: invokeAttMap.put(name, methods[j]);
254: }
255: }
256: }
257: }
258:
259: /**
260: * XXX Find if the 'className' is the name of the MBean or
261: * the real class ( I suppose first )
262: * XXX Read (optional) descriptions from a .properties, generated
263: * from source
264: * XXX Deal with constructors
265: *
266: * @param registry The Bean registry (not used)
267: * @param domain The bean domain (not used)
268: * @param realClass The class to analyze
269: * @param type The bean type
270: * @return ManagedBean The create MBean
271: */
272: public ManagedBean createManagedBean(Registry registry,
273: String domain, Class realClass, String type) {
274: ManagedBean mbean = new ManagedBean();
275:
276: Method methods[] = null;
277:
278: Hashtable attMap = new Hashtable();
279: // key: attribute val: getter method
280: Hashtable getAttMap = new Hashtable();
281: // key: attribute val: setter method
282: Hashtable setAttMap = new Hashtable();
283: // key: operation val: invoke method
284: Hashtable invokeAttMap = new Hashtable();
285:
286: methods = realClass.getMethods();
287:
288: initMethods(realClass, methods, attMap, getAttMap, setAttMap,
289: invokeAttMap);
290:
291: try {
292:
293: Enumeration en = attMap.keys();
294: while (en.hasMoreElements()) {
295: String name = (String) en.nextElement();
296: AttributeInfo ai = new AttributeInfo();
297: ai.setName(name);
298: Method gm = (Method) getAttMap.get(name);
299: if (gm != null) {
300: //ai.setGetMethodObj( gm );
301: ai.setGetMethod(gm.getName());
302: Class t = gm.getReturnType();
303: if (t != null)
304: ai.setType(t.getName());
305: }
306: Method sm = (Method) setAttMap.get(name);
307: if (sm != null) {
308: //ai.setSetMethodObj(sm);
309: Class t = sm.getParameterTypes()[0];
310: if (t != null)
311: ai.setType(t.getName());
312: ai.setSetMethod(sm.getName());
313: }
314: ai.setDescription("Introspected attribute " + name);
315: if (log.isDebugEnabled())
316: log.debug("Introspected attribute " + name + " "
317: + gm + " " + sm);
318: if (gm == null)
319: ai.setReadable(false);
320: if (sm == null)
321: ai.setWriteable(false);
322: if (sm != null || gm != null)
323: mbean.addAttribute(ai);
324: }
325:
326: en = invokeAttMap.keys();
327: while (en.hasMoreElements()) {
328: String name = (String) en.nextElement();
329: Method m = (Method) invokeAttMap.get(name);
330: if (m != null && name != null) {
331: OperationInfo op = new OperationInfo();
332: op.setName(name);
333: op.setReturnType(m.getReturnType().getName());
334: op.setDescription("Introspected operation " + name);
335: Class parms[] = m.getParameterTypes();
336: for (int i = 0; i < parms.length; i++) {
337: ParameterInfo pi = new ParameterInfo();
338: pi.setType(parms[i].getName());
339: pi.setName("param" + i);
340: pi
341: .setDescription("Introspected parameter param"
342: + i);
343: op.addParameter(pi);
344: }
345: mbean.addOperation(op);
346: } else {
347: log.error("Null arg " + name + " " + m);
348: }
349: }
350:
351: Constructor[] constructors = realClass.getConstructors();
352: for (int i = 0; i < constructors.length; i++) {
353: ConstructorInfo info = new ConstructorInfo();
354: String className = realClass.getName();
355: int nIndex = -1;
356: if ((nIndex = className.lastIndexOf('.')) != -1) {
357: className = className.substring(nIndex + 1);
358: }
359: info.setName(className);
360: info.setDescription(constructors[i].getName());
361: Class classes[] = constructors[i].getParameterTypes();
362: for (int j = 0; j < classes.length; j++) {
363: ParameterInfo pi = new ParameterInfo();
364: pi.setType(classes[j].getName());
365: pi.setName("param" + j);
366: pi.setDescription("Introspected parameter param"
367: + j);
368: info.addParameter(pi);
369: }
370: mbean.addConstructor(info);
371: }
372:
373: if (log.isDebugEnabled())
374: log.debug("Setting name: " + type);
375: mbean.setName(type);
376:
377: return mbean;
378: } catch (Exception ex) {
379: ex.printStackTrace();
380: return null;
381: }
382: }
383:
384: // -------------------- Utils --------------------
385: /**
386: * Converts the first character of the given
387: * String into lower-case.
388: *
389: * @param name The string to convert
390: * @return String
391: */
392: private static String unCapitalize(String name) {
393: if (name == null || name.length() == 0) {
394: return name;
395: }
396: char chars[] = name.toCharArray();
397: chars[0] = Character.toLowerCase(chars[0]);
398: return new String(chars);
399: }
400:
401: }
402:
403: // End of class: MbeanDescriptorsIntrospectionSource
|