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 }