1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.system;
23
24 import java.io.IOException;
25 import java.io.StringWriter;
26 import java.io.Writer;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32
33 import javax.management.Attribute;
34 import javax.management.InstanceNotFoundException;
35 import javax.management.MBeanAttributeInfo;
36 import javax.management.MBeanInfo;
37 import javax.management.MBeanServer;
38 import javax.management.ObjectName;
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.transform.Transformer;
42 import javax.xml.transform.TransformerException;
43 import javax.xml.transform.TransformerFactory;
44 import javax.xml.transform.dom.DOMSource;
45 import javax.xml.transform.stream.StreamResult;
46
47 import org.jboss.deployment.DeploymentException;
48 import org.jboss.logging.Logger;
49 import org.jboss.mx.util.JMXExceptionDecoder;
50 import org.jboss.system.metadata.ServiceAttributeMetaData;
51 import org.jboss.system.metadata.ServiceMetaData;
52 import org.jboss.system.metadata.ServiceMetaDataParser;
53 import org.jboss.system.metadata.ServiceValueContext;
54 import org.jboss.util.xml.DOMWriter;
55 import org.w3c.dom.Document;
56 import org.w3c.dom.Element;
57 import org.w3c.dom.Node;
58 import org.w3c.dom.NodeList;
59
60 /**
61 * Service configuration helper.
62 *
63 * @author <a href="mailto:marc@jboss.org">Marc Fleury</a>
64 * @author <a href="mailto:hiram@jboss.org">Hiram Chirino</a>
65 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
66 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
67 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
68 * @author <a href="adrian@jboss.com">Adrian Brock</a>
69 * @version <tt>$Revision: 72444 $</tt>
70 */
71 public class ServiceConfigurator
72 {
73 /** The MBean server which this service is registered in. */
74 private final MBeanServer server;
75
76 /** The parent service controller */
77 private final ServiceController serviceController;
78
79 /** The ServiceCreator */
80 private final ServiceCreator serviceCreator;
81
82 /** The ServiceBinding plugin policy */
83 private ServiceBinding serviceBinding;
84
85 /** Instance logger. */
86 private static final Logger log = Logger.getLogger(ServiceConfigurator.class);
87
88 /**
89 * Configure an MBean
90 *
91 * @param server the server
92 * @param controller the service controller
93 * @param objectName the object name
94 * @param classLoaderName the classloader object name
95 * @param attrs the attributes
96 * @throws Exception for any error
97 */
98 public static void configure(MBeanServer server, ServiceController controller, ObjectName objectName, ObjectName classLoaderName, Collection<ServiceAttributeMetaData> attrs) throws Exception
99 {
100 server = checkMBeanServer(server, controller);
101 ClassLoader cl = server.getClassLoader(classLoaderName);
102 configure(server, controller, objectName, cl, attrs);
103 }
104
105 /**
106 * Configure an MBean
107 *
108 * @param server the server
109 * @param controller the service controller
110 * @param objectName the object name
111 * @param cl the classloader
112 * @param attrs the attributes
113 * @throws Exception for any error
114 */
115 public static void configure(MBeanServer server, ServiceController controller, ObjectName objectName, ClassLoader cl, Collection<ServiceAttributeMetaData> attrs) throws Exception
116 {
117 ServiceValueContext valueContext = new ServiceValueContext(server, controller, cl);
118 server = checkMBeanServer(server, controller);
119
120 HashMap<String, MBeanAttributeInfo> attributeMap = getAttributeMap(server, objectName);
121
122 for (ServiceAttributeMetaData attribute : attrs)
123 {
124 String attributeName = attribute.getName();
125 if (attributeName == null || attributeName.length() == 0)
126 throw new DeploymentException("No or empty attribute name for " + objectName);
127 MBeanAttributeInfo attributeInfo = attributeMap.get(attributeName);
128 if (attributeInfo == null)
129 {
130 throw new DeploymentException("No Attribute found with name: " + attributeName + " for " + objectName
131 +", attributes: "+attributeMap.keySet());
132 }
133
134 valueContext.setAttributeInfo(attributeInfo);
135 Object value = null;
136 ClassLoader previous = SecurityActions.setContextClassLoader(cl);
137 try
138 {
139 value = attribute.getValue(valueContext);
140 }
141 finally
142 {
143 SecurityActions.resetContextClassLoader(previous);
144 }
145 try
146 {
147 log.debug(attributeName + " set to " + value + " in " + objectName);
148 server.setAttribute(objectName, new Attribute(attributeName, value));
149 }
150 catch (Throwable t)
151 {
152 throw new DeploymentException("Exception setting attribute " + attributeName + " on mbean " + objectName, JMXExceptionDecoder.decode(t));
153 }
154 }
155 }
156
157 /**
158 * Check the server/controller parameters
159 *
160 * @param server the server
161 * @param controller the controller
162 * @return the server
163 */
164 private static MBeanServer checkMBeanServer(MBeanServer server, ServiceController controller)
165 {
166 if (server != null)
167 return server;
168
169 if (controller != null)
170 return controller.getMBeanServer();
171
172 throw new IllegalArgumentException("Either the server or controller must be passed");
173 }
174
175 /**
176 * Apply any service binding overrides
177 *
178 * @review why isn't the MBeanServer part of this api?
179 * @param server the server
180 * @param objectName the object name
181 * @param serviceBinding the service binding
182 * @throws Exception for any error
183 */
184 public static void applyServiceConfig(MBeanServer server, ObjectName objectName, ServiceBinding serviceBinding) throws Exception
185 {
186 try
187 {
188 serviceBinding.applyServiceConfig(objectName);
189 }
190 catch (Throwable e)
191 {
192 // serviceBinding is most probably a dynamic mbean proxy
193 Throwable t = JMXExceptionDecoder.decode(e);
194 log.warn("Failed to apply service binding override for " + objectName, t);
195 }
196 }
197
198 /**
199 * Get an attribute map for the MBean
200 *
201 * @param server the server
202 * @param objectName the object name
203 * @return a map of attribute name to attribute info
204 * @throws Exception for any error
205 */
206 private static HashMap<String, MBeanAttributeInfo> getAttributeMap(MBeanServer server, ObjectName objectName) throws Exception
207 {
208 MBeanInfo info;
209 try
210 {
211 info = server.getMBeanInfo(objectName);
212 }
213 catch (InstanceNotFoundException e)
214 {
215 // The MBean is no longer available
216 throw new DeploymentException("Trying to configure nonexistent mbean: " + objectName);
217 }
218 catch (Exception e)
219 {
220 throw new DeploymentException("Could not get mbeanInfo", JMXExceptionDecoder.decode(e));
221 }
222 if (info == null)
223 throw new DeploymentException("MBeanInfo is null for mbean: " + objectName);
224 MBeanAttributeInfo[] attributes = info.getAttributes();
225 HashMap<String, MBeanAttributeInfo> attributeMap = new HashMap<String, MBeanAttributeInfo>();
226 for (int i = 0; i < attributes.length; i++)
227 {
228 MBeanAttributeInfo attr = attributes[i];
229 attributeMap.put(attr.getName(), attr);
230 }
231
232 return attributeMap;
233 }
234
235 /**
236 * Constructor
237 *
238 * @deprecated the service controller no longer uses the service configurator and vice-versa
239 * @param server the mbean server
240 * @param serviceController the servie controller
241 * @param serviceCreator the service creator
242 */
243 public ServiceConfigurator(MBeanServer server, ServiceController serviceController, ServiceCreator serviceCreator)
244 {
245 if (server == null)
246 throw new IllegalArgumentException("Null server");
247 if (serviceCreator == null)
248 throw new IllegalArgumentException("Null serverCreator");
249
250 this.server = server;
251 this.serviceController = serviceController;
252 this.serviceCreator = serviceCreator;
253 if (serviceController != null)
254 this.serviceBinding = serviceController.getServiceBinding();
255 }
256
257 /**
258 * Dynamically plug-in a ServiceBinding policy
259 * to possibly override the configuration of a service.
260 *
261 * @param serviceBinding policy
262 */
263 public void setServiceBinding(ServiceBinding serviceBinding)
264 {
265 this.serviceBinding = serviceBinding;
266 }
267
268 /**
269 * The <code>install</code> method iterates through the mbean tags in the
270 * supplied xml configuration and creates and configures the mbeans shown.
271 * The mbean configuration can be nested.
272 *
273 * @deprecated the service controller no longer uses the service configurator and vice-versa
274 * @param config the xml <code>Element</code> containing the configuration of
275 * the mbeans to create and configure.
276 * @param loaderName the classloader's ObjectName
277 * @return a <code>List</code> of ObjectNames of created mbeans.
278 * @throws DeploymentException if an error occurs
279 */
280 public List<ObjectName> install(Element config, ObjectName loaderName) throws DeploymentException
281 {
282 // Parse the xml
283 ServiceMetaDataParser parser = new ServiceMetaDataParser(config);
284 List<ServiceMetaData> metaDatas = parser.parse();
285
286 // Track the registered object names
287 List<ObjectName> result = new ArrayList<ObjectName>(metaDatas.size());
288
289 // Go through each mbean in the passed xml
290 for (ServiceMetaData metaData : metaDatas)
291 {
292 ObjectName objectName = metaData.getObjectName();
293 Collection<ServiceAttributeMetaData> attrs = metaData.getAttributes();
294 // Install and configure the mbean
295 try
296 {
297 ServiceCreator.install(server, objectName, metaData, null);
298 result.add(objectName);
299 configure(server, null, objectName, loaderName, attrs);
300 if (serviceBinding != null)
301 applyServiceConfig(server, objectName, serviceBinding);
302 }
303 catch (Throwable t)
304 {
305 // Something went wrong
306 for (ObjectName name : result)
307 {
308 try
309 {
310 serviceCreator.remove(name);
311 }
312 catch (Exception e)
313 {
314 log.error("Error removing mbean after failed deployment: " + name, e);
315 }
316 }
317 DeploymentException.rethrowAsDeploymentException("Error during install", t);
318 }
319 }
320 return result;
321 }
322
323 /**
324 * Builds a string that consists of the configuration elements of the
325 * currently running MBeans registered in the server.
326 *
327 * @todo replace with more sophisticated mbean persistence mechanism.
328 * @param server the MBeanServer
329 * @param serviceController the service controller
330 * @param objectNames the object names to retrieve
331 * @return the xml string
332 * @throws Exception Failed to construct configuration.
333 */
334 public static String getConfiguration(MBeanServer server, ServiceController serviceController, ObjectName[] objectNames) throws Exception
335 {
336 Writer out = new StringWriter();
337
338 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
339 DocumentBuilder builder = factory.newDocumentBuilder();
340 Document doc = builder.newDocument();
341
342 Element serverElement = doc.createElement("server");
343
344 // Store attributes as XML
345 for (int j = 0; j < objectNames.length; j++)
346 {
347 Element mbeanElement = internalGetConfiguration(doc, server, serviceController, objectNames[j]);
348 serverElement.appendChild(mbeanElement);
349 }
350
351 doc.appendChild(serverElement);
352
353 // Write configuration
354 new DOMWriter(out).setPrettyprint(true).print(doc);
355
356 out.close();
357
358 // Return configuration
359 return out.toString();
360 }
361
362 private static Element internalGetConfiguration(Document doc, MBeanServer server, ServiceController serviceController, ObjectName name) throws Exception
363 {
364 Element mbeanElement = doc.createElement("mbean");
365 mbeanElement.setAttribute("name", name.toString());
366
367 MBeanInfo info = server.getMBeanInfo(name);
368 mbeanElement.setAttribute("code", info.getClassName());
369 MBeanAttributeInfo[] attributes = info.getAttributes();
370 boolean trace = log.isTraceEnabled();
371 for (int i = 0; i < attributes.length; i++)
372 {
373 if (trace)
374 log.trace("considering attribute: " + attributes[i]);
375 if (attributes[i].isReadable() && attributes[i].isWritable())
376 {
377 Element attributeElement = null;
378 if (attributes[i].getType().equals("javax.management.ObjectName"))
379 {
380 attributeElement = doc.createElement("depends");
381 attributeElement.setAttribute("optional-attribute-name", attributes[i].getName());
382 }
383 else
384 {
385 attributeElement = doc.createElement("attribute");
386 attributeElement.setAttribute("name", attributes[i].getName());
387 }
388 Object value = server.getAttribute(name, attributes[i].getName());
389
390 if (value != null)
391 {
392 if (value instanceof Element)
393 {
394 attributeElement.appendChild(doc.importNode((Element) value, true));
395 }
396 else
397 {
398 attributeElement.appendChild(doc.createTextNode(value.toString()));
399 }
400 }
401 mbeanElement.appendChild(attributeElement);
402 }
403 }
404
405 ServiceContext sc = serviceController.getServiceContext(name);
406 for (Iterator i = sc.iDependOn.iterator(); i.hasNext();)
407 {
408 ServiceContext needs = (ServiceContext) i.next();
409 Element dependsElement = doc.createElement("depends");
410 dependsElement.appendChild(doc.createTextNode(needs.objectName.toString()));
411 mbeanElement.appendChild(dependsElement);
412 }
413
414 return mbeanElement;
415 }
416
417 /**
418 * Builds a string that consists of the configuration elements of the
419 * currently running MBeans registered in the server.
420 *
421 * TODO replace with more sophisticated mbean persistence mechanism.
422 * @param objectNames the object names
423 * @return the xml string
424 * @throws Exception Failed to construct configuration.
425 */
426 public String getConfiguration(ObjectName[] objectNames) throws Exception
427 {
428 return getConfiguration(server, serviceController, objectNames);
429 }
430
431 /**
432 * A utility method that transforms the contents of the argument element into
433 * a StringBuffer representation that can be reparsed.
434 *
435 * [FIXME] This is not a general DOMUtils method because of its funny contract. It does not
436 * support multiple child elements neither can it deal with text content.
437 *
438 * @param element - the parent dom element whose contents are to be extracted as an xml document string.
439 * @return the xml document string.
440 * @throws IOException for an error during IO
441 * @throws TransformerException for an erro during transformation
442 */
443 public static StringBuffer getElementContent(Element element) throws IOException, TransformerException
444 {
445 NodeList children = element.getChildNodes();
446 Element content = null;
447 for (int n = 0; n < children.getLength(); n++)
448 {
449 Node node = children.item(n);
450 if (node.getNodeType() == Node.ELEMENT_NODE)
451 {
452 content = (Element)node;
453 break;
454 }
455 }
456 if (content == null)
457 return null;
458
459 // Get a parsable representation of this elements content
460 DOMSource source = new DOMSource(content);
461 TransformerFactory tFactory = TransformerFactory.newInstance();
462 Transformer transformer = tFactory.newTransformer();
463 StringWriter sw = new StringWriter();
464 StreamResult result = new StreamResult(sw);
465 transformer.transform(source, result);
466 sw.close();
467 return sw.getBuffer();
468 }
469 }