/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.tomcat.util.modeler.modules; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.management.Attribute; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.loading.MLet; import javax.xml.transform.TransformerException; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.DomUtil; import org.apache.tomcat.util.modeler.AttributeInfo; import org.apache.tomcat.util.modeler.BaseModelMBean; import org.apache.tomcat.util.modeler.ManagedBean; import org.apache.tomcat.util.modeler.Registry; import org.w3c.dom.Document; import org.w3c.dom.Node; /** This will create mbeans based on a config file. * The format is an extended version of MLET. * * Classloading. We don't support any explicit classloader tag. * A ClassLoader is just an mbean ( it can be the standard MLetMBean or * a custom one ). * * XXX add a special attribute to reference the loader mbean, * XXX figure out how to deal with private loaders */ public class MbeansSource extends ModelerSource implements MbeansSourceMBean { private static Log log = LogFactory.getLog(MbeansSource.class); Registry registry; String type; // true if we are during the original loading boolean loading=true; List mbeans=new ArrayList(); static boolean loaderLoaded=false; private Document document; private HashMap object2Node = new HashMap(); long lastUpdate; long updateInterval=10000; // 10s public void setRegistry(Registry reg) { this.registry=reg; } public void setLocation( String loc ) { this.location=loc; } /** Used if a single component is loaded * * @param type */ public void setType( String type ) { this.type=type; } public void setSource( Object source ) { this.source=source; } public Object getSource() { return source; } public String getLocation() { return location; } /** Return the list of mbeans created by this source. * It can be used to implement runtime services. */ public List getMBeans() { return mbeans; } public List loadDescriptors( Registry registry, String location, String type, Object source) throws Exception { setRegistry(registry); setLocation(location); setType(type); setSource(source); execute(); return mbeans; } public void start() throws Exception { registry.invoke(mbeans, "start", false); } public void stop() throws Exception { registry.invoke(mbeans, "stop", false); } public void init() throws Exception { if( mbeans==null) execute(); if( registry==null ) registry=Registry.getRegistry(); registry.invoke(mbeans, "init", false); } public void destroy() throws Exception { registry.invoke(mbeans, "destroy", false); } public void load() throws Exception { execute(); // backward compat } public void execute() throws Exception { if( registry==null ) registry=Registry.getRegistry(); try { InputStream stream=getInputStream(); long t1=System.currentTimeMillis(); document = DomUtil.readXml(stream); // We don't care what the root node is. Node descriptorsN=document.getDocumentElement(); if( descriptorsN == null ) { log.error("No descriptors found"); return; } Node firstMbeanN=DomUtil.getChild(descriptorsN, null); if( firstMbeanN==null ) { // maybe we have a single mlet if( log.isDebugEnabled() ) log.debug("No child " + descriptorsN); firstMbeanN=descriptorsN; } MBeanServer server=(MBeanServer)Registry.getServer(); // XXX Not very clean... Just a workaround if( ! loaderLoaded ) { // Register a loader that will be find ant classes. ObjectName defaultLoader= new ObjectName("modeler", "loader", "modeler"); MLet mlet=new MLet( new URL[0], this.getClass().getClassLoader()); server.registerMBean(mlet, defaultLoader); loaderLoaded=true; } // Process nodes for (Node mbeanN = firstMbeanN; mbeanN != null; mbeanN= DomUtil.getNext(mbeanN, null, Node.ELEMENT_NODE)) { String nodeName=mbeanN.getNodeName(); // mbean is the "official" name if( "mbean".equals(nodeName) || "MLET".equals(nodeName) ) { String code=DomUtil.getAttribute( mbeanN, "code" ); String objectName=DomUtil.getAttribute( mbeanN, "objectName" ); if( objectName==null ) { objectName=DomUtil.getAttribute( mbeanN, "name" ); } if( log.isDebugEnabled()) log.debug( "Processing mbean objectName=" + objectName + " code=" + code); // args can be grouped in constructor or direct childs Node constructorN=DomUtil.getChild(mbeanN, "constructor"); if( constructorN == null ) constructorN=mbeanN; processArg(constructorN); try { ObjectName oname=new ObjectName(objectName); if( ! server.isRegistered( oname )) { // We wrap everything in a model mbean. // XXX need to support "StandardMBeanDescriptorsSource" String modelMBean=BaseModelMBean.class.getName(); server.createMBean(modelMBean, oname, new Object[] { code, this}, new String[] { String.class.getName(), ModelerSource.class.getName() } ); mbeans.add(oname); } object2Node.put( oname, mbeanN ); // XXX Arguments, loader !!! } catch( Exception ex ) { log.error( "Error creating mbean " + objectName, ex); } Node firstAttN=DomUtil.getChild(mbeanN, "attribute"); for (Node descN = firstAttN; descN != null; descN = DomUtil.getNext( descN )) { processAttribute(server, descN, objectName); } } else if("jmx-operation".equals(nodeName) ) { String name=DomUtil.getAttribute(mbeanN, "objectName"); if( name==null ) name=DomUtil.getAttribute(mbeanN, "name"); String operation=DomUtil.getAttribute(mbeanN, "operation"); if( log.isDebugEnabled()) log.debug( "Processing invoke objectName=" + name + " code=" + operation); try { ObjectName oname=new ObjectName(name); processArg( mbeanN ); server.invoke( oname, operation, null, null); } catch (Exception e) { log.error( "Error in invoke " + name + " " + operation); } } ManagedBean managed=new ManagedBean(); DomUtil.setAttributes(managed, mbeanN); Node firstN; // process attribute info firstN=DomUtil.getChild( mbeanN, "attribute"); for (Node descN = firstN; descN != null; descN = DomUtil.getNext( descN )) { AttributeInfo ci=new AttributeInfo(); DomUtil.setAttributes(ci, descN); managed.addAttribute( ci ); } } long t2=System.currentTimeMillis(); log.info( "Reading mbeans " + (t2-t1)); loading=false; } catch( Exception ex ) { log.error( "Error reading mbeans ", ex); } } public void updateField( ObjectName oname, String name, Object value ) { if( loading ) return; // nothing by default //log.info( "XXX UpdateField " + oname + " " + name + " " + value); Node n=(Node)object2Node.get( oname ); if( n == null ) { log.info( "Node not found " + oname ); return; } Node attNode=DomUtil.findChildWithAtt(n, "attribute", "name", name); if( attNode == null ) { // found no existing attribute with this name attNode=n.getOwnerDocument().createElement("attribute"); DomUtil.setAttribute(attNode, "name", name); n.appendChild(attNode); } String oldValue=DomUtil.getAttribute(attNode, "value"); if( oldValue != null ) { // we'll convert all values to text content DomUtil.removeAttribute( attNode, "value"); } DomUtil.setText(attNode, value.toString()); //store(); } /** Store the mbeans. * XXX add a background thread to store it periodically */ public void save() { // XXX customize no often than ( based on standard descriptor ), etc. // It doesn't work very well if we call this on each set att - // the triger will work for the first att, but all others will be delayed long time=System.currentTimeMillis(); if( location!=null && time - lastUpdate > updateInterval ) { lastUpdate=time; try { FileOutputStream fos=new FileOutputStream(location); DomUtil.writeXml(document, fos); } catch (TransformerException e) { log.error( "Error writing"); } catch (FileNotFoundException e) { log.error( "Error writing" ,e ); } } } private void processAttribute(MBeanServer server, Node descN, String objectName ) { String attName=DomUtil.getAttribute(descN, "name"); String value=DomUtil.getAttribute(descN, "value"); String type=null; // DomUtil.getAttribute(descN, "type"); if( value==null ) { // The value may be specified as CDATA value=DomUtil.getContent(descN); } try { if( log.isDebugEnabled()) log.debug("Set attribute " + objectName + " " + attName + " " + value); ObjectName oname=new ObjectName(objectName); // find the type if( type==null ) type=registry.getType( oname, attName ); if( type==null ) { log.info("Can't find attribute " + objectName + " " + attName ); } else { Object valueO=registry.convertValue( type, value); server.setAttribute(oname, new Attribute(attName, valueO)); } } catch( Exception ex) { log.error("Error processing attribute " + objectName + " " + attName + " " + value, ex); } } private void processArg(Node mbeanN) { Node firstArgN=DomUtil.getChild(mbeanN, "arg" ); // process all args for (Node argN = firstArgN; argN != null; argN = DomUtil.getNext( argN )) { String type=DomUtil.getAttribute(argN, "type"); String value=DomUtil.getAttribute(argN, "value"); if( value==null ) { // The value may be specified as CDATA value=DomUtil.getContent(argN); } } } }