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    {
043        private static Log log = LogFactory.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)
073                throws Exception
074        {
075            setRegistry(registry);
076            setLocation(location);
077            setType(type);
078            setSource(source);
079            execute();
080            return mbeans;
081        }
082    
083        public void execute() throws Exception {
084            if( registry==null ) registry=Registry.getRegistry();
085            try {
086                ManagedBean managed=createManagedBean(registry, null, (Class)source, type);
087                if( managed==null ) return;
088                managed.setName( type );
089    
090                mbeans.add(managed);
091    
092            } catch( Exception ex ) {
093                log.error( "Error reading descriptors ", ex);
094            }
095        }
096    
097    
098    
099        // ------------ Implementation for non-declared introspection classes
100    
101        static Hashtable specialMethods=new Hashtable();
102        static {
103            specialMethods.put( "preDeregister", "");
104            specialMethods.put( "postDeregister", "");
105        }
106    
107        private static String strArray[]=new String[0];
108        private static ObjectName objNameArray[]=new ObjectName[0];
109        // createMBean == registerClass + registerMBean
110    
111        private static Class[] supportedTypes  = new Class[] {
112            Boolean.class,
113            Boolean.TYPE,
114            Byte.class,
115            Byte.TYPE,
116            Character.class,
117            Character.TYPE,
118            Short.class,
119            Short.TYPE,
120            Integer.class,
121            Integer.TYPE,
122            Long.class,
123            Long.TYPE,
124            Float.class, 
125            Float.TYPE,
126            Double.class,
127            Double.TYPE,
128            String.class,
129            strArray.getClass(),
130            BigDecimal.class,
131            BigInteger.class,
132            ObjectName.class,
133            objNameArray.getClass(),
134            java.io.File.class,
135        };
136        
137        /**
138         * Check if this class is one of the supported types.
139         * If the class is supported, returns true.  Otherwise,
140         * returns false.
141         * @param ret The class to check
142         * @return boolean True if class is supported
143         */ 
144        private boolean supportedType(Class ret) {
145            for (int i = 0; i < supportedTypes.length; i++) {
146                if (ret == supportedTypes[i]) {
147                    return true;
148                }
149            }
150            if (isBeanCompatible(ret)) {
151                return true;
152            }
153            return false;
154        }
155    
156        /**
157         * Check if this class conforms to JavaBeans specifications.
158         * If the class is conformant, returns true.
159         *
160         * @param javaType The class to check
161         * @return boolean True if the class is compatible.
162         */
163        protected boolean isBeanCompatible(Class javaType) {
164            // Must be a non-primitive and non array
165            if (javaType.isArray() || javaType.isPrimitive()) {
166                return false;
167            }
168    
169            // Anything in the java or javax package that
170            // does not have a defined mapping is excluded.
171            if (javaType.getName().startsWith("java.") || 
172                javaType.getName().startsWith("javax.")) {
173                return false;
174            }
175    
176            try {
177                javaType.getConstructor(new Class[]{});
178            } catch (java.lang.NoSuchMethodException e) {
179                return false;
180            }
181    
182            // Make sure superclass is compatible
183            Class superClass = javaType.getSuperclass();
184            if (superClass != null && 
185                superClass != java.lang.Object.class && 
186                superClass != java.lang.Exception.class && 
187                superClass != java.lang.Throwable.class) {
188                if (!isBeanCompatible(superClass)) {
189                    return false;
190                }
191            }
192            return true;
193        }
194        
195        /** 
196         * Process the methods and extract 'attributes', methods, etc
197         *
198         * @param realClass The class to process
199         * @param methods The methods to process
200         * @param attMap The attribute map (complete)
201         * @param getAttMap The readable attributess map
202         * @param setAttMap The settable attributes map
203         * @param invokeAttMap The invokable attributes map
204         */
205        private void initMethods(Class realClass,
206                                 Method methods[],
207                                 Hashtable attMap, Hashtable getAttMap,
208                                 Hashtable setAttMap, Hashtable invokeAttMap)
209        {
210            for (int j = 0; j < methods.length; ++j) {
211                String name=methods[j].getName();
212    
213                if( Modifier.isStatic(methods[j].getModifiers()))
214                    continue;
215                if( ! Modifier.isPublic( methods[j].getModifiers() ) ) {
216                    if( log.isDebugEnabled())
217                        log.debug("Not public " + methods[j] );
218                    continue;
219                }
220                if( methods[j].getDeclaringClass() == Object.class )
221                    continue;
222                Class params[]=methods[j].getParameterTypes();
223    
224                if( name.startsWith( "get" ) && params.length==0) {
225                    Class ret=methods[j].getReturnType();
226                    if( ! supportedType( ret ) ) {
227                        if( log.isDebugEnabled() )
228                            log.debug("Unsupported type " + methods[j]);
229                        continue;
230                    }
231                    name=unCapitalize( name.substring(3));
232    
233                    getAttMap.put( name, methods[j] );
234                    // just a marker, we don't use the value
235                    attMap.put( name, methods[j] );
236                } else if( name.startsWith( "is" ) && params.length==0) {
237                    Class ret=methods[j].getReturnType();
238                    if( Boolean.TYPE != ret  ) {
239                        if( log.isDebugEnabled() )
240                            log.debug("Unsupported type " + methods[j] + " " + ret );
241                        continue;
242                    }
243                    name=unCapitalize( name.substring(2));
244    
245                    getAttMap.put( name, methods[j] );
246                    // just a marker, we don't use the value
247                    attMap.put( name, methods[j] );
248    
249                } else if( name.startsWith( "set" ) && params.length==1) {
250                    if( ! supportedType( params[0] ) ) {
251                        if( log.isDebugEnabled() )
252                            log.debug("Unsupported type " + methods[j] + " " + params[0]);
253                        continue;
254                    }
255                    name=unCapitalize( name.substring(3));
256                    setAttMap.put( name, methods[j] );
257                    attMap.put( name, methods[j] );
258                } else {
259                    if( params.length == 0 ) {
260                        if( specialMethods.get( methods[j].getName() ) != null )
261                            continue;
262                        invokeAttMap.put( name, methods[j]);
263                    } else {
264                        boolean supported=true;
265                        for( int i=0; i<params.length; i++ ) {
266                            if( ! supportedType( params[i])) {
267                                supported=false;
268                                break;
269                            }
270                        }
271                        if( supported )
272                            invokeAttMap.put( name, methods[j]);
273                    }
274                }
275            }
276        }
277    
278        /**
279         * XXX Find if the 'className' is the name of the MBean or
280         *       the real class ( I suppose first )
281         * XXX Read (optional) descriptions from a .properties, generated
282         *       from source
283         * XXX Deal with constructors
284         *
285         * @param registry The Bean registry (not used)
286         * @param domain The bean domain (not used)
287         * @param realClass The class to analyze
288         * @param type The bean type
289         * @return ManagedBean The create MBean
290         */
291        public ManagedBean createManagedBean(Registry registry, String domain,
292                                             Class realClass, String type)
293        {
294            ManagedBean mbean= new ManagedBean();
295    
296            Method methods[]=null;
297    
298            Hashtable attMap=new Hashtable();
299            // key: attribute val: getter method
300            Hashtable getAttMap=new Hashtable();
301            // key: attribute val: setter method
302            Hashtable setAttMap=new Hashtable();
303            // key: operation val: invoke method
304            Hashtable invokeAttMap=new Hashtable();
305    
306            methods = realClass.getMethods();
307    
308            initMethods(realClass, methods, attMap, getAttMap, setAttMap, invokeAttMap );
309    
310            try {
311    
312                Enumeration en=attMap.keys();
313                while( en.hasMoreElements() ) {
314                    String name=(String)en.nextElement();
315                    AttributeInfo ai=new AttributeInfo();
316                    ai.setName( name );
317                    Method gm=(Method)getAttMap.get(name);
318                    if( gm!=null ) {
319                        //ai.setGetMethodObj( gm );
320                        ai.setGetMethod( gm.getName());
321                        Class t=gm.getReturnType();
322                        if( t!=null )
323                            ai.setType( t.getName() );
324                    }
325                    Method sm=(Method)setAttMap.get(name);
326                    if( sm!=null ) {
327                        //ai.setSetMethodObj(sm);
328                        Class t=sm.getParameterTypes()[0];
329                        if( t!=null )
330                            ai.setType( t.getName());
331                        ai.setSetMethod( sm.getName());
332                    }
333                    ai.setDescription("Introspected attribute " + name);
334                    if( log.isDebugEnabled()) log.debug("Introspected attribute " +
335                            name + " " + gm + " " + sm);
336                    if( gm==null )
337                        ai.setReadable(false);
338                    if( sm==null )
339                        ai.setWriteable(false);
340                    if( sm!=null || gm!=null )
341                        mbean.addAttribute(ai);
342                }
343    
344                en=invokeAttMap.keys();
345                while( en.hasMoreElements() ) {
346                    String name=(String)en.nextElement();
347                    Method m=(Method)invokeAttMap.get(name);
348                    if( m!=null && name != null ) {
349                        OperationInfo op=new OperationInfo();
350                        op.setName(name);
351                        op.setReturnType(m.getReturnType().getName());
352                        op.setDescription("Introspected operation " + name);
353                        Class parms[]=m.getParameterTypes();
354                        for(int i=0; i<parms.length; i++ ) {
355                            ParameterInfo pi=new ParameterInfo();
356                            pi.setType(parms[i].getName());
357                            pi.setName( "param" + i);
358                            pi.setDescription("Introspected parameter param" + i);
359                            op.addParameter(pi);
360                        }
361                        mbean.addOperation(op);
362                    } else {
363                        log.error("Null arg " + name + " " + m );
364                    }
365                }
366    
367                Constructor[] constructors = realClass.getConstructors();
368                for(int i=0;i<constructors.length;i++) {
369                    ConstructorInfo info = new ConstructorInfo();
370                    String className = realClass.getName();
371                    int nIndex = -1;
372                    if((nIndex = className.lastIndexOf('.'))!=-1) {
373                        className = className.substring(nIndex+1);
374                    }
375                    info.setName(className);
376                    info.setDescription(constructors[i].getName());
377                    Class classes[] = constructors[i].getParameterTypes();
378                    for(int j=0;j<classes.length;j++) {
379                        ParameterInfo pi = new ParameterInfo();
380                        pi.setType(classes[j].getName());
381                        pi.setName("param" + j);
382                        pi.setDescription("Introspected parameter param" + j);
383                        info.addParameter(pi);
384                    }
385                    mbean.addConstructor(info);
386                }
387                
388                if( log.isDebugEnabled())
389                    log.debug("Setting name: " + type );
390                mbean.setName( type );
391    
392                return mbean;
393            } catch( Exception ex ) {
394                ex.printStackTrace();
395                return null;
396            }
397        }
398    
399    
400        // -------------------- Utils --------------------
401        /**
402         * Converts the first character of the given
403         * String into lower-case.
404         *
405         * @param name The string to convert
406         * @return String
407         */
408        private static String unCapitalize(String name) {
409            if (name == null || name.length() == 0) {
410                return name;
411            }
412            char chars[] = name.toCharArray();
413            chars[0] = Character.toLowerCase(chars[0]);
414            return new String(chars);
415        }
416    
417    }
418    
419    // End of class: MbeanDescriptorsIntrospectionSource