1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.catalina.ant.jmx;
19
20 import java.io.IOException;
21 import java.lang.reflect.Array;
22 import java.net.InetAddress;
23 import java.net.MalformedURLException;
24 import java.net.UnknownHostException;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Properties;
30 import java.util.Set;
31 import java.util.StringTokenizer;
32
33 import javax.management.MBeanServerConnection;
34 import javax.management.MalformedObjectNameException;
35 import javax.management.ObjectName;
36 import javax.management.openmbean.CompositeData;
37 import javax.management.openmbean.CompositeDataSupport;
38 import javax.management.openmbean.CompositeType;
39 import javax.management.openmbean.OpenType;
40 import javax.management.openmbean.SimpleType;
41 import javax.management.openmbean.TabularDataSupport;
42 import javax.management.remote.JMXConnector;
43 import javax.management.remote.JMXConnectorFactory;
44 import javax.management.remote.JMXServiceURL;
45
46 import org.apache.catalina.ant.BaseRedirectorHelperTask;
47 import org.apache.tools.ant.BuildException;
48 import org.apache.tools.ant.Project;
49
50 /**
51 * Access <em>JMX</em> JSR 160 MBeans Server.
52 * <ul>
53 * <li>open more then one JSR 160 rmi connection</li>
54 * <li>Get/Set Mbeans attributes</li>
55 * <li>Call Mbean Operation with arguments</li>
56 * <li>Argument values can be converted from string to
57 * int,long,float,double,boolean,ObjectName or InetAddress</li>
58 * <li>Query Mbeans</li>
59 * <li>Show Get, Call, Query result at Ant console log</li>
60 * <li>Bind Get, Call, Query result at Ant properties</li>
61 * </ul>
62 *
63 * Examples: open server with reference and autorisation
64 *
65 * <pre>
66 *
67 * <jmxOpen
68 * host="127.0.0.1"
69 * port="9014"
70 * username="monitorRole"
71 * password="mysecret"
72 * ref="jmx.myserver"
73 * />
74 *
75 * </pre>
76 *
77 * All calls after opening with same refid reuse the connection.
78 * <p>
79 * First call to a remote MBeanserver save the JMXConnection a referenz
80 * <em>jmx.server</em>
81 * </p>
82 * All JMXAccessorXXXTask support the attribute <em>if</em> and
83 * <em>unless</em>. With <em>if</em> the task is only execute when property
84 * exist and with <em>unless</em> when property not exists. <br/><b>NOTE
85 * </b>: These tasks require Ant 1.6 or later interface.
86 *
87 * @author Peter Rossbach
88 * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
89 * @since 5.5.10
90 */
91
92 public class JMXAccessorTask extends BaseRedirectorHelperTask {
93
94 // ----------------------------------------------------- Instance Variables
95
96 public static String JMX_SERVICE_PREFIX = "service:jmx:rmi:///jndi/rmi://";
97
98 public static String JMX_SERVICE_SUFFIX = "/jmxrmi";
99
100 private String name = null;
101
102 private String resultproperty;
103
104 private String url = null;
105
106 private String host = "localhost";
107
108 private String port = "8050";
109
110 private String password = null;
111
112 private String username = null;
113
114 private String ref = "jmx.server";
115
116 private boolean echo = false;
117
118 private boolean separatearrayresults = true;
119
120 private String delimiter;
121
122 private String unlessCondition;
123
124 private String ifCondition;
125
126 private Properties properties = new Properties();
127
128 // ----------------------------------------------------- Instance Info
129
130 /**
131 * Descriptive information describing this implementation.
132 */
133 private static final String info = "org.apache.catalina.ant.JMXAccessorTask/1.1";
134
135 /**
136 * Return descriptive information about this implementation and the
137 * corresponding version number, in the format
138 * <code><description>/<version></code>.
139 */
140 public String getInfo() {
141
142 return (info);
143
144 }
145
146 // ------------------------------------------------------------- Properties
147
148 /**
149 * The name used at remote MbeanServer
150 */
151
152 public String getName() {
153 return (this.name);
154 }
155
156 public void setName(String objectName) {
157 this.name = objectName;
158 }
159
160 /**
161 * @return Returns the resultproperty.
162 */
163 public String getResultproperty() {
164 return resultproperty;
165 }
166
167 /**
168 * @param propertyName The resultproperty to set.
169 */
170 public void setResultproperty(String propertyName) {
171 this.resultproperty = propertyName;
172 }
173
174 /**
175 * @return Returns the delimiter.
176 */
177 public String getDelimiter() {
178 return delimiter;
179 }
180
181 /**
182 * @param separator The delimiter to set.
183 */
184 public void setDelimiter(String separator) {
185 this.delimiter = separator;
186 }
187
188 /**
189 * @return Returns the echo.
190 */
191 public boolean isEcho() {
192 return echo;
193 }
194
195 /**
196 * @param echo
197 * The echo to set.
198 */
199 public void setEcho(boolean echo) {
200 this.echo = echo;
201 }
202
203 /**
204 * @return Returns the separatearrayresults.
205 */
206 public boolean isSeparatearrayresults() {
207 return separatearrayresults;
208 }
209
210 /**
211 * @param separateArrayResults
212 * The separatearrayresults to set.
213 */
214 public void setSeparatearrayresults(boolean separateArrayResults) {
215 this.separatearrayresults = separateArrayResults;
216 }
217
218 /**
219 * The login password for the <code>Manager</code> application.
220 */
221 public String getPassword() {
222 return (this.password);
223 }
224
225 public void setPassword(String password) {
226 this.password = password;
227 }
228
229 /**
230 * The login username for the <code>JMX</code> MBeanServer.
231 */
232 public String getUsername() {
233 return (this.username);
234 }
235
236 public void setUsername(String username) {
237 this.username = username;
238 }
239
240 /**
241 * The URL of the <code>JMX JSR 160</code> MBeanServer to be used.
242 */
243
244 public String getUrl() {
245 return (this.url);
246 }
247
248 public void setUrl(String url) {
249 this.url = url;
250 }
251
252 /**
253 * The Host of the <code>JMX JSR 160</code> MBeanServer to be used.
254 */
255
256 public String getHost() {
257 return (this.host);
258 }
259
260 public void setHost(String host) {
261 this.host = host;
262 }
263
264 /**
265 * The Port of the <code>JMX JSR 160</code> MBeanServer to be used.
266 */
267
268 public String getPort() {
269 return (this.port);
270 }
271
272 public void setPort(String port) {
273 this.port = port;
274 }
275
276 /**
277 * @return Returns the useRef.
278 */
279 public boolean isUseRef() {
280 return ref != null && !"".equals(ref);
281 }
282
283 /**
284 * @return Returns the ref.
285 */
286 public String getRef() {
287 return ref;
288 }
289
290 /**
291 * @param refId The ref to set.
292 */
293 public void setRef(String refId) {
294 this.ref = refId;
295 }
296
297 /**
298 * @return Returns the ifCondition.
299 */
300 public String getIf() {
301 return ifCondition;
302 }
303
304 /**
305 * Only execute if a property of the given name exists in the current
306 * project.
307 *
308 * @param c property name
309 */
310 public void setIf(String c) {
311 ifCondition = c;
312 }
313
314 /**
315 * @return Returns the unlessCondition.
316 */
317 public String getUnless() {
318 return unlessCondition;
319 }
320
321 /**
322 * Only execute if a property of the given name does not exist in the
323 * current project.
324 *
325 * @param c property name
326 */
327 public void setUnless(String c) {
328 unlessCondition = c;
329 }
330
331 // --------------------------------------------------------- Public Methods
332
333 /**
334 * Execute the specified command. This logic only performs the common
335 * attribute validation required by all subclasses; it does not perform any
336 * functional logic directly.
337 *
338 * @exception BuildException
339 * if a validation error occurs
340 */
341 public void execute() throws BuildException {
342 if (testIfCondition() && testUnlessCondition()) {
343 try {
344 String error = null;
345
346 MBeanServerConnection jmxServerConnection = getJMXConnection();
347 error = jmxExecute(jmxServerConnection);
348 if (error != null && isFailOnError()) {
349 // exception should be thrown only if failOnError == true
350 // or error line will be logged twice
351 throw new BuildException(error);
352 }
353 } catch (Throwable t) {
354 if (isFailOnError()) {
355 throw new BuildException(t);
356 } else {
357 handleErrorOutput(t.getMessage());
358 }
359 } finally {
360 closeRedirector();
361 }
362 }
363 }
364
365 /**
366 * create a new JMX Connection with auth when username and password is set.
367 */
368 public static MBeanServerConnection createJMXConnection(String url,
369 String host, String port, String username, String password)
370 throws MalformedURLException, IOException {
371 String urlForJMX;
372 if (url != null)
373 urlForJMX = url;
374 else
375 urlForJMX = JMX_SERVICE_PREFIX + host + ":" + port
376 + JMX_SERVICE_SUFFIX;
377 Map environment = null;
378 if (username != null && password != null) {
379 String[] credentials = new String[2];
380 credentials[0] = username;
381 credentials[1] = password;
382 environment = new HashMap();
383 environment.put(JMXConnector.CREDENTIALS, credentials);
384 }
385 return JMXConnectorFactory.connect(new JMXServiceURL(urlForJMX),
386 environment).getMBeanServerConnection();
387
388 }
389
390 /**
391 * test the if condition
392 *
393 * @return true if there is no if condition, or the named property exists
394 */
395 protected boolean testIfCondition() {
396 if (ifCondition == null || "".equals(ifCondition)) {
397 return true;
398 }
399 return getProperty(ifCondition) != null;
400 }
401
402 /**
403 * test the unless condition
404 *
405 * @return true if there is no unless condition, or there is a named
406 * property but it doesn't exist
407 */
408 protected boolean testUnlessCondition() {
409 if (unlessCondition == null || "".equals(unlessCondition)) {
410 return true;
411 }
412 return getProperty(unlessCondition) == null;
413 }
414
415 /**
416 * Get Current Connection from <em>ref</em> parameter or create a new one!
417 *
418 * @return The server connection
419 * @throws MalformedURLException
420 * @throws IOException
421 */
422 public static MBeanServerConnection accessJMXConnection(Project project,
423 String url, String host, String port, String username,
424 String password, String refId) throws MalformedURLException,
425 IOException {
426 MBeanServerConnection jmxServerConnection = null;
427 boolean isRef = project != null && refId != null && refId.length() > 0;
428 if (isRef) {
429 Object pref = project.getReference(refId);
430 try {
431 jmxServerConnection = (MBeanServerConnection) pref;
432 } catch (ClassCastException cce) {
433 if (project != null) {
434 project.log("wrong object reference " + refId + " - "
435 + pref.getClass());
436 }
437 return null;
438 }
439 }
440 if (jmxServerConnection == null) {
441 jmxServerConnection = createJMXConnection(url, host, port,
442 username, password);
443 }
444 if (isRef && jmxServerConnection != null) {
445 project.addReference(refId, jmxServerConnection);
446 }
447 return jmxServerConnection;
448 }
449
450 // ------------------------------------------------------ protected Methods
451
452 /**
453 * get JMXConnection
454 *
455 * @return The connection
456 * @throws MalformedURLException
457 * @throws IOException
458 */
459 protected MBeanServerConnection getJMXConnection()
460 throws MalformedURLException, IOException {
461
462 MBeanServerConnection jmxServerConnection = null;
463 if (isUseRef()) {
464 Object pref = null ;
465 if(getProject() != null) {
466 pref = getProject().getReference(getRef());
467 if (pref != null) {
468 try {
469 jmxServerConnection = (MBeanServerConnection) pref;
470 } catch (ClassCastException cce) {
471 getProject().log(
472 "Wrong object reference " + getRef() + " - "
473 + pref.getClass());
474 return null;
475 }
476 }
477 }
478 if (jmxServerConnection == null) {
479 jmxServerConnection = accessJMXConnection(getProject(),
480 getUrl(), getHost(), getPort(), getUsername(),
481 getPassword(), getRef());
482 }
483 } else {
484 jmxServerConnection = accessJMXConnection(getProject(), getUrl(),
485 getHost(), getPort(), getUsername(), getPassword(), null);
486 }
487 return jmxServerConnection;
488 }
489
490 /**
491 * Execute the specified command, based on the configured properties. The
492 * input stream will be closed upon completion of this task, whether it was
493 * executed successfully or not.
494 *
495 * @exception Exception
496 * if an error occurs
497 */
498 public String jmxExecute(MBeanServerConnection jmxServerConnection)
499 throws Exception {
500
501 if ((jmxServerConnection == null)) {
502 throw new BuildException("Must open a connection!");
503 } else if (isEcho()) {
504 handleOutput("JMX Connection ref=" + ref + " is open!");
505 }
506 return null;
507 }
508
509 /**
510 * Convert string to datatype FIXME How we can transfer values from ant
511 * project reference store (ref)?
512 *
513 * @param value The value
514 * @param valueType The type
515 * @return The converted object
516 */
517 protected Object convertStringToType(String value, String valueType) {
518 if ("java.lang.String".equals(valueType))
519 return value;
520
521 Object convertValue = value;
522 if ("java.lang.Integer".equals(valueType) || "int".equals(valueType)) {
523 try {
524 convertValue = new Integer(value);
525 } catch (NumberFormatException ex) {
526 if (isEcho())
527 handleErrorOutput("Unable to convert to integer:" + value);
528 }
529 } else if ("java.lang.Long".equals(valueType)
530 || "long".equals(valueType)) {
531 try {
532 convertValue = new Long(value);
533 } catch (NumberFormatException ex) {
534 if (isEcho())
535 handleErrorOutput("Unable to convert to long:" + value);
536 }
537 } else if ("java.lang.Boolean".equals(valueType)
538 || "boolean".equals(valueType)) {
539 convertValue = new Boolean(value);
540 } else if ("java.lang.Float".equals(valueType)
541 || "float".equals(valueType)) {
542 try {
543 convertValue = new Float(value);
544 } catch (NumberFormatException ex) {
545 if (isEcho())
546 handleErrorOutput("Unable to convert to float:" + value);
547 }
548 } else if ("java.lang.Double".equals(valueType)
549 || "double".equals(valueType)) {
550 try {
551 convertValue = new Double(value);
552 } catch (NumberFormatException ex) {
553 if (isEcho())
554 handleErrorOutput("Unable to convert to double:" + value);
555 }
556 } else if ("javax.management.ObjectName".equals(valueType)
557 || "name".equals(valueType)) {
558 try {
559 convertValue = new ObjectName(value);
560 } catch (MalformedObjectNameException e) {
561 if (isEcho())
562 handleErrorOutput("Unable to convert to ObjectName:"
563 + value);
564 }
565 } else if ("java.net.InetAddress".equals(valueType)) {
566 try {
567 convertValue = InetAddress.getByName(value);
568 } catch (UnknownHostException exc) {
569 if (isEcho())
570 handleErrorOutput("Unable to resolve host name:" + value);
571 }
572 }
573 return convertValue;
574 }
575
576 /**
577 * @param name context of result
578 * @param result
579 */
580 protected void echoResult(String name, Object result) {
581 if (isEcho()) {
582 if (result.getClass().isArray()) {
583 for (int i = 0; i < Array.getLength(result); i++) {
584 handleOutput(name + "." + i + "=" + Array.get(result, i));
585 }
586 } else
587 handleOutput(name + "=" + result);
588 }
589 }
590
591 /**
592 * create result as property with name from attribute resultproperty
593 *
594 * @param result The result
595 * @see #createProperty(String, Object)
596 */
597 protected void createProperty(Object result) {
598 if (resultproperty != null) {
599 createProperty(resultproperty, result);
600 }
601 }
602
603 /**
604 * create result as property with name from property prefix When result is
605 * an array and isSeparateArrayResults is true, resultproperty used as
606 * prefix (<code>resultproperty.0-array.length</code> and store the
607 * result array length at <code>resultproperty.length</code>. Other
608 * option is that you delemit your result with a delimiter
609 * (java.util.StringTokenizer is used).
610 *
611 * @param propertyPrefix
612 * @param result
613 */
614 protected void createProperty(String propertyPrefix, Object result) {
615 if (propertyPrefix == null)
616 propertyPrefix = "";
617 if (result instanceof CompositeDataSupport) {
618 CompositeDataSupport data = (CompositeDataSupport) result;
619 CompositeType compositeType = data.getCompositeType();
620 Set keys = compositeType.keySet();
621 for (Iterator iter = keys.iterator(); iter.hasNext();) {
622 String key = (String) iter.next();
623 Object value = data.get(key);
624 OpenType type = compositeType.getType(key);
625 if (type instanceof SimpleType) {
626 setProperty(propertyPrefix + "." + key, value);
627 } else {
628 createProperty(propertyPrefix + "." + key, value);
629 }
630 }
631 } else if (result instanceof TabularDataSupport) {
632 TabularDataSupport data = (TabularDataSupport) result;
633 for (Iterator iter = data.keySet().iterator(); iter.hasNext();) {
634 Object key = iter.next();
635 for (Iterator iter1 = ((List) key).iterator(); iter1.hasNext();) {
636 Object key1 = iter1.next();
637 CompositeData valuedata = data.get(new Object[] { key1 });
638 Object value = valuedata.get("value");
639 OpenType type = valuedata.getCompositeType().getType(
640 "value");
641 if (type instanceof SimpleType) {
642 setProperty(propertyPrefix + "." + key1, value);
643 } else {
644 createProperty(propertyPrefix + "." + key1, value);
645 }
646 }
647 }
648 } else if (result.getClass().isArray()) {
649 if (isSeparatearrayresults()) {
650 int size = 0;
651 for (int i = 0; i < Array.getLength(result); i++) {
652 if (setProperty(propertyPrefix + "." + size, Array.get(
653 result, i))) {
654 size++;
655 }
656 }
657 if (size > 0) {
658 setProperty(propertyPrefix + ".Length", Integer
659 .toString(size));
660 }
661 }
662 } else {
663 String delim = getDelimiter();
664 if (delim != null) {
665 StringTokenizer tokenizer = new StringTokenizer(result
666 .toString(), delim);
667 int size = 0;
668 for (; tokenizer.hasMoreTokens();) {
669 String token = tokenizer.nextToken();
670 if (setProperty(propertyPrefix + "." + size, token)) {
671 size++;
672 }
673 }
674 if (size > 0)
675 setProperty(propertyPrefix + ".Length", Integer
676 .toString(size));
677 } else {
678 setProperty(propertyPrefix, result.toString());
679 }
680 }
681 }
682
683 /**
684 * get all properties, when project is there got all project Properties
685 * @return properties
686 */
687 public Map getProperties() {
688 Project currentProject = getProject();
689 if (currentProject != null) {
690 return currentProject.getProperties();
691 } else {
692 return properties;
693 }
694 }
695
696 /**
697 * get all Properties
698 * @param property
699 * @return The property
700 */
701 public String getProperty(String property) {
702 Project currentProject = getProject();
703 if (currentProject != null) {
704 return currentProject.getProperty(property);
705 } else {
706 return properties.getProperty(property);
707 }
708 }
709
710 /**
711 * @param property The property
712 * @param value The value
713 * @return True if successful
714 */
715 public boolean setProperty(String property, Object value) {
716 if (property != null) {
717 if (value == null)
718 value = "";
719 if (isEcho()) {
720 handleOutput(property + "=" + value.toString());
721 }
722 Project currentProject = getProject();
723 if (currentProject != null) {
724 currentProject.setNewProperty(property, value.toString());
725 } else {
726 properties.setProperty(property, value.toString());
727 }
728 return true;
729 }
730 return false;
731 }
732 }