/* * 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.catalina.ant.jmx; import java.io.IOException; import java.lang.reflect.Array; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenType; import javax.management.openmbean.SimpleType; import javax.management.openmbean.TabularDataSupport; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import org.apache.catalina.ant.BaseRedirectorHelperTask; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; /** * Access <em>JMX</em> JSR 160 MBeans Server. * <ul> * <li>open more then one JSR 160 rmi connection</li> * <li>Get/Set Mbeans attributes</li> * <li>Call Mbean Operation with arguments</li> * <li>Argument values can be converted from string to * int,long,float,double,boolean,ObjectName or InetAddress</li> * <li>Query Mbeans</li> * <li>Show Get, Call, Query result at Ant console log</li> * <li>Bind Get, Call, Query result at Ant properties</li> * </ul> * * Examples: open server with reference and autorisation * * <pre> * * <jmxOpen * host="127.0.0.1" * port="9014" * username="monitorRole" * password="mysecret" * ref="jmx.myserver" * /> * * </pre> * * All calls after opening with same refid reuse the connection. * <p> * First call to a remote MBeanserver save the JMXConnection a referenz * <em>jmx.server</em> * </p> * All JMXAccessorXXXTask support the attribute <em>if</em> and * <em>unless</em>. With <em>if</em> the task is only execute when property * exist and with <em>unless</em> when property not exists. <br/><b>NOTE * </b>: These tasks require Ant 1.6 or later interface. * * @author Peter Rossbach * * @since 5.5.10 */ public class JMXAccessorTask extends BaseRedirectorHelperTask { // ----------------------------------------------------- Instance Variables public static String JMX_SERVICE_PREFIX = "service:jmx:rmi:///jndi/rmi://"; public static String JMX_SERVICE_SUFFIX = "/jmxrmi"; private String name = null; private String resultproperty; private String url = null; private String host = "localhost"; private String port = "8050"; private String password = null; private String username = null; private String ref = "jmx.server"; private boolean echo = false; private boolean separatearrayresults = true; private String delimiter; private String unlessCondition; private String ifCondition; private Properties properties = new Properties(); // ----------------------------------------------------- Instance Info /** * Descriptive information describing this implementation. */ private static final String info = "org.apache.catalina.ant.JMXAccessorTask/1.1"; /** * Return descriptive information about this implementation and the * corresponding version number, in the format * <code><description>/<version></code>. */ public String getInfo() { return (info); } // ------------------------------------------------------------- Properties /** * The name used at remote MbeanServer */ public String getName() { return (this.name); } public void setName(String objectName) { this.name = objectName; } /** * @return Returns the resultproperty. */ public String getResultproperty() { return resultproperty; } /** * @param propertyName The resultproperty to set. */ public void setResultproperty(String propertyName) { this.resultproperty = propertyName; } /** * @return Returns the delimiter. */ public String getDelimiter() { return delimiter; } /** * @param separator The delimiter to set. */ public void setDelimiter(String separator) { this.delimiter = separator; } /** * @return Returns the echo. */ public boolean isEcho() { return echo; } /** * @param echo * The echo to set. */ public void setEcho(boolean echo) { this.echo = echo; } /** * @return Returns the separatearrayresults. */ public boolean isSeparatearrayresults() { return separatearrayresults; } /** * @param separateArrayResults * The separatearrayresults to set. */ public void setSeparatearrayresults(boolean separateArrayResults) { this.separatearrayresults = separateArrayResults; } /** * The login password for the <code>Manager</code> application. */ public String getPassword() { return (this.password); } public void setPassword(String password) { this.password = password; } /** * The login username for the <code>JMX</code> MBeanServer. */ public String getUsername() { return (this.username); } public void setUsername(String username) { this.username = username; } /** * The URL of the <code>JMX JSR 160</code> MBeanServer to be used. */ public String getUrl() { return (this.url); } public void setUrl(String url) { this.url = url; } /** * The Host of the <code>JMX JSR 160</code> MBeanServer to be used. */ public String getHost() { return (this.host); } public void setHost(String host) { this.host = host; } /** * The Port of the <code>JMX JSR 160</code> MBeanServer to be used. */ public String getPort() { return (this.port); } public void setPort(String port) { this.port = port; } /** * @return Returns the useRef. */ public boolean isUseRef() { return ref != null && !"".equals(ref); } /** * @return Returns the ref. */ public String getRef() { return ref; } /** * @param refId The ref to set. */ public void setRef(String refId) { this.ref = refId; } /** * @return Returns the ifCondition. */ public String getIf() { return ifCondition; } /** * Only execute if a property of the given name exists in the current * project. * * @param c property name */ public void setIf(String c) { ifCondition = c; } /** * @return Returns the unlessCondition. */ public String getUnless() { return unlessCondition; } /** * Only execute if a property of the given name does not exist in the * current project. * * @param c property name */ public void setUnless(String c) { unlessCondition = c; } // --------------------------------------------------------- Public Methods /** * Execute the specified command. This logic only performs the common * attribute validation required by all subclasses; it does not perform any * functional logic directly. * * @exception BuildException * if a validation error occurs */ public void execute() throws BuildException { if (testIfCondition() && testUnlessCondition()) { try { String error = null; MBeanServerConnection jmxServerConnection = getJMXConnection(); error = jmxExecute(jmxServerConnection); if (error != null && isFailOnError()) { // exception should be thrown only if failOnError == true // or error line will be logged twice throw new BuildException(error); } } catch (Throwable t) { if (isFailOnError()) { throw new BuildException(t); } else { handleErrorOutput(t.getMessage()); } } finally { closeRedirector(); } } } /** * create a new JMX Connection with auth when username and password is set. */ public static MBeanServerConnection createJMXConnection(String url, String host, String port, String username, String password) throws MalformedURLException, IOException { String urlForJMX; if (url != null) urlForJMX = url; else urlForJMX = JMX_SERVICE_PREFIX + host + ":" + port + JMX_SERVICE_SUFFIX; Map environment = null; if (username != null && password != null) { String[] credentials = new String[2]; credentials[0] = username; credentials[1] = password; environment = new HashMap(); environment.put(JMXConnector.CREDENTIALS, credentials); } return JMXConnectorFactory.connect(new JMXServiceURL(urlForJMX), environment).getMBeanServerConnection(); } /** * test the if condition * * @return true if there is no if condition, or the named property exists */ protected boolean testIfCondition() { if (ifCondition == null || "".equals(ifCondition)) { return true; } return getProperty(ifCondition) != null; } /** * test the unless condition * * @return true if there is no unless condition, or there is a named * property but it doesn't exist */ protected boolean testUnlessCondition() { if (unlessCondition == null || "".equals(unlessCondition)) { return true; } return getProperty(unlessCondition) == null; } /** * Get Current Connection from <em>ref</em> parameter or create a new one! * * @return The server connection * @throws MalformedURLException * @throws IOException */ public static MBeanServerConnection accessJMXConnection(Project project, String url, String host, String port, String username, String password, String refId) throws MalformedURLException, IOException { MBeanServerConnection jmxServerConnection = null; boolean isRef = project != null && refId != null && refId.length() > 0; if (isRef) { Object pref = project.getReference(refId); try { jmxServerConnection = (MBeanServerConnection) pref; } catch (ClassCastException cce) { if (project != null) { project.log("wrong object reference " + refId + " - " + pref.getClass()); } return null; } } if (jmxServerConnection == null) { jmxServerConnection = createJMXConnection(url, host, port, username, password); } if (isRef && jmxServerConnection != null) { project.addReference(refId, jmxServerConnection); } return jmxServerConnection; } // ------------------------------------------------------ protected Methods /** * get JMXConnection * * @return The connection * @throws MalformedURLException * @throws IOException */ protected MBeanServerConnection getJMXConnection() throws MalformedURLException, IOException { MBeanServerConnection jmxServerConnection = null; if (isUseRef()) { Object pref = null ; if(getProject() != null) { pref = getProject().getReference(getRef()); if (pref != null) { try { jmxServerConnection = (MBeanServerConnection) pref; } catch (ClassCastException cce) { getProject().log( "Wrong object reference " + getRef() + " - " + pref.getClass()); return null; } } } if (jmxServerConnection == null) { jmxServerConnection = accessJMXConnection(getProject(), getUrl(), getHost(), getPort(), getUsername(), getPassword(), getRef()); } } else { jmxServerConnection = accessJMXConnection(getProject(), getUrl(), getHost(), getPort(), getUsername(), getPassword(), null); } return jmxServerConnection; } /** * Execute the specified command, based on the configured properties. The * input stream will be closed upon completion of this task, whether it was * executed successfully or not. * * @exception Exception * if an error occurs */ public String jmxExecute(MBeanServerConnection jmxServerConnection) throws Exception { if ((jmxServerConnection == null)) { throw new BuildException("Must open a connection!"); } else if (isEcho()) { handleOutput("JMX Connection ref=" + ref + " is open!"); } return null; } /** * Convert string to datatype FIXME How we can transfer values from ant * project reference store (ref)? * * @param value The value * @param valueType The type * @return The converted object */ protected Object convertStringToType(String value, String valueType) { if ("java.lang.String".equals(valueType)) return value; Object convertValue = value; if ("java.lang.Integer".equals(valueType) || "int".equals(valueType)) { try { convertValue = new Integer(value); } catch (NumberFormatException ex) { if (isEcho()) handleErrorOutput("Unable to convert to integer:" + value); } } else if ("java.lang.Long".equals(valueType) || "long".equals(valueType)) { try { convertValue = new Long(value); } catch (NumberFormatException ex) { if (isEcho()) handleErrorOutput("Unable to convert to long:" + value); } } else if ("java.lang.Boolean".equals(valueType) || "boolean".equals(valueType)) { convertValue = new Boolean(value); } else if ("java.lang.Float".equals(valueType) || "float".equals(valueType)) { try { convertValue = new Float(value); } catch (NumberFormatException ex) { if (isEcho()) handleErrorOutput("Unable to convert to float:" + value); } } else if ("java.lang.Double".equals(valueType) || "double".equals(valueType)) { try { convertValue = new Double(value); } catch (NumberFormatException ex) { if (isEcho()) handleErrorOutput("Unable to convert to double:" + value); } } else if ("javax.management.ObjectName".equals(valueType) || "name".equals(valueType)) { try { convertValue = new ObjectName(value); } catch (MalformedObjectNameException e) { if (isEcho()) handleErrorOutput("Unable to convert to ObjectName:" + value); } } else if ("java.net.InetAddress".equals(valueType)) { try { convertValue = InetAddress.getByName(value); } catch (UnknownHostException exc) { if (isEcho()) handleErrorOutput("Unable to resolve host name:" + value); } } return convertValue; } /** * @param name context of result * @param result */ protected void echoResult(String name, Object result) { if (isEcho()) { if (result.getClass().isArray()) { for (int i = 0; i < Array.getLength(result); i++) { handleOutput(name + "." + i + "=" + Array.get(result, i)); } } else handleOutput(name + "=" + result); } } /** * create result as property with name from attribute resultproperty * * @param result The result * @see #createProperty(String, Object) */ protected void createProperty(Object result) { if (resultproperty != null) { createProperty(resultproperty, result); } } /** * create result as property with name from property prefix When result is * an array and isSeparateArrayResults is true, resultproperty used as * prefix (<code>resultproperty.0-array.length</code> and store the * result array length at <code>resultproperty.length</code>. Other * option is that you delemit your result with a delimiter * (java.util.StringTokenizer is used). * * @param propertyPrefix * @param result */ protected void createProperty(String propertyPrefix, Object result) { if (propertyPrefix == null) propertyPrefix = ""; if (result instanceof CompositeDataSupport) { CompositeDataSupport data = (CompositeDataSupport) result; CompositeType compositeType = data.getCompositeType(); Set keys = compositeType.keySet(); for (Iterator iter = keys.iterator(); iter.hasNext();) { String key = (String) iter.next(); Object value = data.get(key); OpenType type = compositeType.getType(key); if (type instanceof SimpleType) { setProperty(propertyPrefix + "." + key, value); } else { createProperty(propertyPrefix + "." + key, value); } } } else if (result instanceof TabularDataSupport) { TabularDataSupport data = (TabularDataSupport) result; for (Iterator iter = data.keySet().iterator(); iter.hasNext();) { Object key = iter.next(); for (Iterator iter1 = ((List) key).iterator(); iter1.hasNext();) { Object key1 = iter1.next(); CompositeData valuedata = data.get(new Object[] { key1 }); Object value = valuedata.get("value"); OpenType type = valuedata.getCompositeType().getType( "value"); if (type instanceof SimpleType) { setProperty(propertyPrefix + "." + key1, value); } else { createProperty(propertyPrefix + "." + key1, value); } } } } else if (result.getClass().isArray()) { if (isSeparatearrayresults()) { int size = 0; for (int i = 0; i < Array.getLength(result); i++) { if (setProperty(propertyPrefix + "." + size, Array.get( result, i))) { size++; } } if (size > 0) { setProperty(propertyPrefix + ".Length", Integer .toString(size)); } } } else { String delim = getDelimiter(); if (delim != null) { StringTokenizer tokenizer = new StringTokenizer(result .toString(), delim); int size = 0; for (; tokenizer.hasMoreTokens();) { String token = tokenizer.nextToken(); if (setProperty(propertyPrefix + "." + size, token)) { size++; } } if (size > 0) setProperty(propertyPrefix + ".Length", Integer .toString(size)); } else { setProperty(propertyPrefix, result.toString()); } } } /** * get all properties, when project is there got all project Properties * @return properties */ public Map getProperties() { Project currentProject = getProject(); if (currentProject != null) { return currentProject.getProperties(); } else { return properties; } } /** * get all Properties * @param property * @return The property */ public String getProperty(String property) { Project currentProject = getProject(); if (currentProject != null) { return currentProject.getProperty(property); } else { return properties.getProperty(property); } } /** * @param property The property * @param value The value * @return True if successful */ public boolean setProperty(String property, Object value) { if (property != null) { if (value == null) value = ""; if (isEcho()) { handleOutput(property + "=" + value.toString()); } Project currentProject = getProject(); if (currentProject != null) { currentProject.setNewProperty(property, value.toString()); } else { properties.setProperty(property, value.toString()); } return true; } return false; } }