|
|||
Java ArticleSupercharging BeanShell with AntAuthor: Pankaj Kumar
Last Revision Date: 7/26/05, Publication Date: 7/26/05
AbstractThis article explains how to execute any Ant task from within a BeanShell script, bringing the rich and growing Ant task library to BeanShell programmers and, in the process, vastly increasing its potential for serious scripting. This kind of bridging is good for Ant as well, allowing Ant tasks to be used within familiar control and looping constructs. MotivationLong before Java based scripting languages became the in thing and started hogging serious media attention, BeanShell, a free and nifty tool created by Patrick Niemeyer, allowed us all the benefits of scripting languages with the familiar Java syntax and access to Java class libraries. Personally, I have used it many times over the years for quick prototyping and experimentation tasks. Although BeanShell enhances productivity for Java programmers as it is, the lack of a rich cross-platform script library has limited its usefulness, especially for general purpose admin and automation tasks. In fact, I have seen Ant scripts being used for such purposes. Such uses of Ant, although frowned upon, have been justified under scenarios where availability of JDK and Ant can be safely assumed. Ant comes with a huge collection of cross-platform automation tasks, either built-in or through integration with other tools, and most Java programmers are familiar with these tasks. However, Ant is not designed for general scripting and such usage are awkward at best. Many solutions have been devised to address this problem. One solution is to embed one of many scripting languages supported by Ant through Script task. As BeanShell is one of those languages, you can execute BeanShell code from within an Ant script. Another solution is Jelly, a java and XML based scripting and processing engine. Jelly tags allow conditional statements and looping constructs for Ant tasks and target to create general purpose scripts. Although both of these solutions work, I find the resulting programs too verbose, awkward to write (and understand) and, in general, inelegant. What would be really cool is a seamless way to call Ant tasks from BeanShell scripts. Not finding anything like this on the Web, so one weekedn I explored the possibility of creating such an integration myself. Fortunately, BeanShell's mechanism to invoke arbitrary commands and Ant's Task API made the task really simple. Running Ant Tasks From BeanShell Prompt
Within few hours I had the guts of the integration ready through a
scripted object, which
I named C:\>java -cp bsh-2.0b1.jar;%ANT_HOME%\lib\ant.jar bsh.Interpreter BeanShell 2.0b1.1 - by Pat Niemeyer (pat@pat.net) bsh % source("ant.bsh"); bsh % ant = ant();and execute Echo Ant task:
bsh % ant.echo().message("How do you do?").execute(); [echo] How do you do? bsh %Compare this invocation with the equivalent Ant script fragment: <echo message="How do you do?"/>Unarguably, the BeanShell syntax is no simpler than the corresponding Ant syntax. However, it does have a direct and simple mapping with the Ant syntax. I kept it this way to maximize the reuse of Ant task documentation and keep my code simple. Let me illustrate this with another Ant script fragment: <copy todir="tmp" filtering="true"> <fileset dir="." includes="*.xml"/> <filterset> <filtersfile file="filters.props"/> </filterset> </copy>This script copies all files with extension xml in the current directory to tmp
sub-directory, replacing tokens within '@ ' symbol (such as @author@ ) to
token values specified in file filters.props .
The corresponding BeanShell statements are: bsh % copy = ant.copy().todir("tmp").filtering("true"); bsh % copy.fileset().dir(".").includes("*.xml"); bsh % copy.filterset().filtersfile().file("filters.props"); bsh % copy.execute();It is possible to shorten the previous sequence of commands with the following single command: bsh % copy = ant.copy().todir("tmp").filtering("true").fileset() .dir(".").includes("*.xml").parent.filterset().filtersfile() .file("filters.props").execute();Here is another BeanShell command making use of Ant task to perform a SSH remote execution: bsh % ant.sshexec().host("hostname").username("user").password("passwd") .command("ls").execute();Note: You should replace hostname , user and passwd in
the previous command with values appropriate for your environment. Also, you would need
jsch.jar to execute sshexec task as mentioned in
Library Dependencies
section of Ant documentation.
Now that we have a feel for how the scripted object Rules to Write BeanShell StatementsAs you might have already deduced from previous examples, BeanShell statements for Ant task invocations are written by observing following simple rules:
The following diagram illustrates element objects, corresponding
Ant objects, their associations and flow of control during
Pay attention to this diagram. This will come handy in understanding the code presented in the next section. Looking at The Code
Rather than presenting the defintion of scripted object /* * Copyright (c) 2005 Pankaj Kumar (pankaj.kumar@gmail.com) * This software is being made available on "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. */ ant(){ if (getResource("/org/apache/tools/ant/Project.class") == null){ print("WARNING: Cannot initialize Ant."); print("WARNING: Perhaps ant.jar is not in CLASSPATH ..."); return null; } project = new org.apache.tools.ant.Project(); project.setCoreLoader(null); project.init(); logger = new org.apache.tools.ant.DefaultLogger(); logger.setOutputPrintStream(System.out); logger.setErrorPrintStream(System.err); logger.setMessageOutputLevel(org.apache.tools.ant.Project.MSG_INFO); project.addBuildListener(logger); project.setBaseDir(new File(".")); tasks = new Properties(); propFilename = "/org/apache/tools/ant/taskdefs/defaults.properties"; tasks.load(tasks.getClass().getResourceAsStream(propFilename)); // Set environment. env_prop = new org.apache.tools.ant.taskdefs.Property(); env_prop.setProject(project); env_prop.setEnvironment("myenv"); env_prop.execute(); invoke(String tname, Object[] args){ clazz = tasks.getProperty(tname); if (clazz == null){ throw new Exception("no such task: " + tname); } tobj = Class.forName(clazz).newInstance(); tobj.setProject(project); tobj.setTaskName(tname); return element(tobj, null); } // need separate method for ant(). May be a BeanShell bug. ant(){ return invoke("ant", new Object[] { }); } convType(String arg, Class type){ if (type.isPrimitive()){ if (type.toString().equals("boolean")){ ucArg = arg.toUpperCase(); return ((ucArg.equals("TRUE") || ucArg.equals("ON") || ucArg.equals("YES")) ? Boolean.TRUE : Boolean.FALSE); } else if (type.toString().equals("int")){ return Integer.valueOf(arg); } else if (type.toString().equals("short")){ return Short.valueOf(arg); } else { print("unknown type: " + type); return null; } } else { ctor = type.getConstructor(new Class[] {arg.getClass()}); return ctor.newInstance(new Object[] { arg }); } } element(aobj, parent){ this.aobj = aobj; this.parent = parent; java.lang.reflect.Method[] methods = aobj.getClass().getMethods(); invoke(String aname, Object[] args){ if (args.length == 0){ // treat aname as an inner element for (method:methods){ mName = method.getName(); if (mName.equalsIgnoreCase("create" + aname)){ return element(method.invoke(aobj, args), this); } else if (mName.equalsIgnoreCase("add" + aname)|| mName.equalsIgnoreCase("addConfigured" + aname)){ Class[] ptypes = method.getParameterTypes(); if (ptypes.length == 1){ args = new Object[] { ptypes[0].newInstance() }; method.invoke(aobj, args); return element(args[0], this); } } } print("no such inner element: " + aname); } else if (args.length == 1){ for (method:methods){ if (method.getName().equalsIgnoreCase("set" + aname) || (aname.equalsIgnoreCase("text") && method.getName().equals("addText")) ){ Class[] ptypes = method.getParameterTypes(); if (ptypes.length != 1){ print("no such attribute: " + aname); return null; } // May need for type conversion if (!ptypes[0].equals(args[0].getClass())){ args[0] = convType(args[0], ptypes[0]); } method.invoke(aobj, args); return this; } } print("no such attribute: " + aname); } else { print("invalid signature for: " + aname); } return null; } // Convenience method, so that one can call // task().innerelem().....execute() // in place of task().innerelem().....parent.execute() execute(){ if (parent == null) aobj.execute(); else parent.execute(); } return this; } String getenv(String key){ return project.getProperty("myenv." + key); } String getenv(String key, String def){ value = project.getProperty("myenv." + key); return (value == null ? def : value); } return this; }
As you can see from the above code, initialization of scripted object
Invocation of One Plus One is More Than TwoThe combination of BeanShell and Ant has more power than the mere sum of their individual powers. This may sound as hyperbole, but is indeed true and can be corroborated through examples: Quick ExperimentationYou no longer need to write a full-fledged Ant script file to experiment with a task or option that you haven't used before. Just type-in the command at BeanShell prompt and see what happens. Reflective Exploration
Access to Ant objects allow you to explore their attributes
and sub-elements using BeanShell's built-in bsh % copy = ant.copy(); bsh % javap(copy.aobj); Recall that member Accessing Environment Variables
Scripted object bsh % print(ant.getenv("OS")); Windows_NT bsh % print(ant.project.getProperties().entrySet()); ... voluminous output skipped ... bsh % Luanching External ProgramsBeanShell command exec() can be used to launch an external program, but it is much less versatile than the Ant exec task. For example, The Ant exec task allows you to
ConclusionIn this article I demonstrated how Ant tasks can be created, configured and executed within BeanShell scripts using a simple scripted object. Such uses allow creation of powerful scripts with very little effort and effectively address limitations of either BeanShell or Ant used alone. Resources
© 2005 by Pankaj Kumar. All Rights Reserved. You may use the content of this article, including the source code, in your programs and documentation. In general, you do not need to contact me for permission unless you are reproducing a significant portion of the article or code for commercial purposes. For example, writing a program that uses the code for personal or company use does not require permission. Republishing this article, even with proper attribution, does require permission. Similarly, incorporating the code in a program that you sell for profit requires permission. You are welcome to send me comments, suggestions or bug fixes. I will look into those as and when time permits and may even publish the good ones on this page. You can contact me by sending e-mail to pankaj.kumar@gmail.com. |