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    package org.apache.commons.modeler.modules;
018    
019    import java.io.FileNotFoundException;
020    import java.io.FileOutputStream;
021    import java.io.InputStream;
022    import java.net.URL;
023    import java.util.ArrayList;
024    import java.util.HashMap;
025    import java.util.List;
026    
027    import javax.management.Attribute;
028    import javax.management.MBeanServer;
029    import javax.management.ObjectName;
030    import javax.management.loading.MLet;
031    import javax.xml.transform.TransformerException;
032    
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    import org.apache.commons.modeler.AttributeInfo;
036    import org.apache.commons.modeler.BaseModelMBean;
037    import org.apache.commons.modeler.ManagedBean;
038    import org.apache.commons.modeler.Registry;
039    import org.apache.commons.modeler.util.DomUtil;
040    import org.w3c.dom.Document;
041    import org.w3c.dom.Node;
042    
043    
044    /** This will create mbeans based on a config file.
045     *  The format is an extended version of MLET.
046     *
047     * Classloading. We don't support any explicit classloader tag. 
048     * A ClassLoader is just an mbean ( it can be the standard MLetMBean or
049     * a custom one ). 
050     * 
051     * XXX add a special attribute to reference the loader mbean,
052     * XXX figure out how to deal with private loaders
053     */
054    public class MbeansSource extends ModelerSource implements MbeansSourceMBean
055    {
056        private static Log log = LogFactory.getLog(MbeansSource.class);
057        Registry registry;
058        String type;
059    
060        // true if we are during the original loading
061        boolean loading=true;
062        List mbeans=new ArrayList();
063        static boolean loaderLoaded=false;
064        private Document document;
065        private HashMap object2Node = new HashMap();
066    
067        long lastUpdate;
068        long updateInterval=10000; // 10s
069    
070        public void setRegistry(Registry reg) {
071            this.registry=reg;
072        }          
073    
074        public void setLocation( String loc ) {
075            this.location=loc;
076        }
077    
078        /** Used if a single component is loaded
079         *
080         * @param type
081         */
082        public void setType( String type ) {
083           this.type=type;
084        }
085    
086        public void setSource( Object source ) {
087            this.source=source;
088        }
089    
090        public Object getSource() {
091            return source;
092        }
093    
094        public String getLocation() {
095            return location;
096        }
097        
098        /** Return the list of mbeans created by this source.
099         *  It can be used to implement runtime services.
100         */
101        public List getMBeans() {
102            return mbeans;
103        }
104    
105        public List loadDescriptors( Registry registry, String location,
106                                     String type, Object source)
107                throws Exception
108        {
109            setRegistry(registry);
110            setLocation(location);
111            setType(type);
112            setSource(source);
113            execute();
114            return mbeans;
115        }
116        
117        public void start() throws Exception {
118            registry.invoke(mbeans, "start", false);        
119        }
120    
121        public void stop() throws Exception {
122            registry.invoke(mbeans, "stop", false);        
123        }
124        
125        public void init() throws Exception {
126            if( mbeans==null) execute();
127            if( registry==null ) registry=Registry.getRegistry();
128            
129            registry.invoke(mbeans, "init", false);
130        }
131        
132        public void destroy() throws Exception {
133            registry.invoke(mbeans, "destroy", false);                
134        }
135        
136        public void load() throws Exception {
137            execute(); // backward compat
138        }
139    
140        public void execute() throws Exception {
141            if( registry==null ) registry=Registry.getRegistry();
142            try {
143                InputStream stream=getInputStream();
144                long t1=System.currentTimeMillis();
145                document = DomUtil.readXml(stream);
146    
147                // We don't care what the root node is.
148                Node descriptorsN=document.getDocumentElement();
149    
150                if( descriptorsN == null ) {
151                    log.error("No descriptors found");
152                    return;
153                }
154    
155                Node firstMbeanN=DomUtil.getChild(descriptorsN, null);
156    
157                if( firstMbeanN==null ) {
158                    // maybe we have a single mlet
159                    if( log.isDebugEnabled() )
160                        log.debug("No child " + descriptorsN);
161                    firstMbeanN=descriptorsN;
162                }
163    
164                MBeanServer server=(MBeanServer)Registry.getServer();
165    
166                // XXX Not very clean...  Just a workaround
167                if( ! loaderLoaded ) {
168                    // Register a loader that will be find ant classes.
169                    ObjectName defaultLoader= new ObjectName("modeler",
170                            "loader", "modeler");
171                    MLet mlet=new MLet( new URL[0], this.getClass().getClassLoader());
172                    server.registerMBean(mlet, defaultLoader);
173                    loaderLoaded=true;
174                }
175            
176                // Process nodes
177                for (Node mbeanN = firstMbeanN; mbeanN != null;
178                     mbeanN= DomUtil.getNext(mbeanN, null, Node.ELEMENT_NODE))
179                {
180                    String nodeName=mbeanN.getNodeName();
181    
182                    // mbean is the "official" name
183                    if( "mbean".equals(nodeName) || "MLET".equals(nodeName) )
184                    {
185                        String code=DomUtil.getAttribute( mbeanN, "code" );
186                        String objectName=DomUtil.getAttribute( mbeanN, "objectName" );
187                        if( objectName==null ) {
188                            objectName=DomUtil.getAttribute( mbeanN, "name" );
189                        }
190                        
191                        if( log.isDebugEnabled())
192                            log.debug( "Processing mbean objectName=" + objectName +
193                                    " code=" + code);
194    
195                        // args can be grouped in constructor or direct childs
196                        Node constructorN=DomUtil.getChild(mbeanN, "constructor");
197                        if( constructorN == null ) constructorN=mbeanN;
198    
199                        ArgsInfo info = processArg(constructorN);
200    
201                        try {
202                            ObjectName oname=new ObjectName(objectName);
203                            if( ! server.isRegistered( oname )) {
204                                // We wrap everything in a model mbean.
205                                // XXX need to support "StandardMBeanDescriptorsSource"
206                                String modelMBean=BaseModelMBean.class.getName();                            
207                                if(info == null) {
208                                    server.createMBean(modelMBean, oname,
209                                                       new Object[] { code, this},
210                                                       new String[] { String.class.getName(),
211                                                                      ModelerSource.class.getName() } 
212                                                       );
213                                } else {
214                                    server.createMBean(modelMBean, oname,
215                                                       new Object[] { code, this,
216                                                                      info.getValues(),
217                                                                      info.getSigs()
218                                                       },
219                                                       new String[] { String.class.getName(),
220                                                                      ModelerSource.class.getName(),
221                                                                      Object[].class.getName(),
222                                                                      String[].class.getName()
223                                                       }
224                                                       );
225                                }
226                                                       
227                                mbeans.add(oname);
228                            }
229                            object2Node.put( oname, mbeanN );
230                            // XXX Arguments, loader !!!
231                        } catch( Exception ex ) {
232                            log.error( "Error creating mbean " + objectName, ex);
233                        }
234    
235                        Node firstAttN=DomUtil.getChild(mbeanN, "attribute");
236                        for (Node descN = firstAttN; descN != null;
237                             descN = DomUtil.getNext( descN ))
238                        {
239                            processAttribute(server, descN, objectName);
240                        }
241                    } else if("jmx-operation".equals(nodeName) ) {
242                        String name=DomUtil.getAttribute(mbeanN, "objectName");
243                        if( name==null )
244                            name=DomUtil.getAttribute(mbeanN, "name");
245    
246                        String operation=DomUtil.getAttribute(mbeanN, "operation");
247    
248                        if( log.isDebugEnabled())
249                            log.debug( "Processing invoke objectName=" + name +
250                                    " code=" + operation);
251                        try {
252                            ObjectName oname=new ObjectName(name);
253    
254                            ArgsInfo info = processArg( mbeanN );
255                            if(info == null) {
256                                server.invoke( oname, operation, null, null);
257                            } else {
258                                server.invoke( oname, operation, info.getValues(), info.getSigs());
259                            }
260                        } catch (Exception e) {
261                            log.error( "Error in invoke " + name + " " + operation);
262                        }
263                    }
264    
265                    ManagedBean managed=new ManagedBean();
266                    DomUtil.setAttributes(managed, mbeanN);
267                    Node firstN;
268    
269                    // process attribute info
270                    firstN=DomUtil.getChild( mbeanN, "attribute");
271                    for (Node descN = firstN; descN != null;
272                         descN = DomUtil.getNext( descN ))
273                    {
274                        AttributeInfo ci=new AttributeInfo();
275                        DomUtil.setAttributes(ci, descN);
276                        managed.addAttribute( ci );
277                    }
278    
279                }
280    
281                long t2=System.currentTimeMillis();
282                log.info( "Reading mbeans  " + (t2-t1));
283                loading=false;
284            } catch( Exception ex ) {
285                log.error( "Error reading mbeans ", ex);
286            }
287        }
288        
289        public void updateField( ObjectName oname, String name, 
290                                 Object value )
291        {
292            if( loading ) return;
293            // nothing by default
294            //log.info( "XXX UpdateField " + oname + " " + name + " " + value);
295            Node n=(Node)object2Node.get( oname );
296            if( n == null ) {
297                log.info( "Node not found " + oname );
298                return;
299            }
300            Node attNode=DomUtil.findChildWithAtt(n, "attribute", "name", name);
301            if( attNode == null ) {
302                // found no existing attribute with this name
303                attNode=n.getOwnerDocument().createElement("attribute");
304                DomUtil.setAttribute(attNode, "name", name);
305                n.appendChild(attNode);
306            } 
307            String oldValue=DomUtil.getAttribute(attNode, "value");
308            if( oldValue != null ) {
309                // we'll convert all values to text content
310                DomUtil.removeAttribute( attNode, "value");
311            }
312            DomUtil.setText(attNode, value.toString());
313    
314            //store();
315        }
316        
317        /** Store the mbeans. 
318         * XXX add a background thread to store it periodically 
319         */ 
320        public void save() {
321            // XXX customize no often than ( based on standard descriptor ), etc.
322            // It doesn't work very well if we call this on each set att - 
323            // the triger will work for the first att, but all others will be delayed
324            long time=System.currentTimeMillis();
325            if( location!=null &&
326                    time - lastUpdate > updateInterval ) {
327                lastUpdate=time;
328                try {
329                    FileOutputStream fos=new FileOutputStream(location);
330                    DomUtil.writeXml(document, fos);
331                } catch (TransformerException e) {
332                    log.error( "Error writing");
333                } catch (FileNotFoundException e) {
334                    log.error( "Error writing" ,e );
335                }
336            }
337        }
338    
339        private void processAttribute(MBeanServer server,
340                                      Node descN, String objectName ) {
341            String attName=DomUtil.getAttribute(descN, "name");
342            String value=DomUtil.getAttribute(descN, "value");
343            String type=null; // DomUtil.getAttribute(descN, "type");
344            if( value==null ) {
345                // The value may be specified as CDATA
346                value=DomUtil.getContent(descN);
347            }
348            try {
349                if( log.isDebugEnabled())
350                    log.debug("Set attribute " + objectName + " " + attName +
351                            " " + value);
352                ObjectName oname=new ObjectName(objectName);
353                // find the type
354                if( type==null )
355                    type=registry.getType(  oname, attName );
356    
357                if( type==null ) {
358                    log.info("Can't find attribute " + objectName + " " + attName );
359    
360                } else {
361                    Object valueO=registry.convertValue( type, value);
362                    server.setAttribute(oname, new Attribute(attName, valueO));
363                }
364            } catch( Exception ex) {
365                log.error("Error processing attribute " + objectName + " " +
366                        attName + " " + value, ex);
367            }
368    
369        }
370    
371        private ArgsInfo processArg(Node mbeanN) {
372            Node firstArgN=DomUtil.getChild(mbeanN, "arg" );
373            if(firstArgN == null) {
374                return null;
375            }
376            ArgsInfo info = new ArgsInfo();
377            // process all args
378            for (Node argN = firstArgN; argN != null;
379                 argN = DomUtil.getNext( argN ))
380            {
381                String type=DomUtil.getAttribute(argN, "type");
382                String value=DomUtil.getAttribute(argN, "value");
383                if( value==null ) {
384                    // The value may be specified as CDATA
385                    value=DomUtil.getContent(argN);
386                }
387                info.addArgPair(type, registry.convertValue(type,value));
388            }
389            return info;
390        }
391    
392        private static class ArgsInfo {
393            private List sigs = new ArrayList();
394            private List values = new ArrayList();
395    
396            ArgsInfo() {
397            }
398    
399            public String [] getSigs() {
400                return (String [])sigs.toArray(new String[sigs.size()]);
401            }
402    
403            public Object[] getValues () {
404                return values.toArray(new Object[values.size()]);
405            }
406    
407            public void addArgPair(String name, Object val) {
408                sigs.add(name);
409                values.add(val);
410            }
411        }
412    }