Tuesday, July 31, 2012

printjar: How to print text file content inside a jar file

Have you ever wonder what's the exact version of your ojdbc6.jar that you have? All the jar files will contain a META-INF/MANIFEST.MF file, and chances are the version will be in it! You may try using $JAVA_HOME/bin/jar -xvf to extract the jar and then view the text file. But afterward you would have to clean up the extracted file so not to liter.
However, if you got Groovy, you can print any text file inside a jar without above mess.
file = new File(args[0])
name = args.size() > 1 ? args[1] : "META-INF/MANIFEST.MF"
jar = new java.util.jar.JarFile(file)
entry = jar.getEntry(name)
istream = jar.getInputStream(entry)
println(istream.text)
istream.close()

You may use this script like this:
$ groovy printjar.groovy /path/to/objdbc6.jar

# Or give an explicit entry name
$ groovy printjar.groovy $JBOSS_HOME/jboss-modules.jar 'META-INF/maven/org.jboss.modules/jboss-modules/pom.properties'
 
UPDATES:
Here are different implementations of similar program in differnet JVM based languages.

Java
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Print text based resource file inside a jar file. (eg: META-INF/MANIFEST.MF)
 *
 * @author Zemian Deng
 */
public class printjar {
    public static void main(String[] args) throws Exception {
        // Search given name in a jar
        JarFile jarFile = new JarFile(args[0]);
        final String searchName = (args.length >= 2) ? args[1]
                : "META-INF/MANIFEST.MF";

        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.getName().contains(searchName)) {
                // Print the JarEntry
                InputStream instream = jarFile.getInputStream(entry);
                try {
                    BufferedReader reader = new BufferedReader(
                            new InputStreamReader(instream));
                    String line = null;
                    while ((line = reader.readLine()) != null) {
                        System.out.println(line);
                    }
                } finally {
                    if (instream != null)
                        instream.close();
                }
            }
        }
    }
}


JavaScript (Run it with jrunscript command)
jar = new java.util.jar.JarFile(arguments[0])
pattern = arguments[arguments.length -1];
entries = jar.entries();
while(entries.hasMoreElements()) {
    jarEntry = entries.nextElement();
    if (jarEntry.toString().search(pattern) != -1) {
        istream = jar.getInputStream(jarEntry);
        cat(istream);
        istream.close();
        break;
    }
}
jar.close();


Kotlin
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.jar.JarFile
val jarFile = JarFile(args[0])
val searchName = if (args.size() >= 2) args[1] else "META-INF/MANIFEST.MF"
for (entry in jarFile.entries()) {
if (entry.getName().contains(searchName)) {
jarFile.getInputStream(entry).use { it ->
val reader = BufferedReader(InputStreamReader(it))
for (line in reader.lineSequence())
println(line)
}
}
}

Sunday, July 29, 2012

Getting jboss-cli.sh to work on MacOSX

If you use MacOSX and JBossAS, then you might have encountered this problem when using the jboss-client.sh tool:


$ bin/jboss-cli.sh 
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect
The controller is not available at localhost:9999

Or you will get this:

$ bin/jboss-cli.sh --connect
org.jboss.as.cli.CliInitializationException: Failed to connect to the controller
at org.jboss.as.cli.impl.CliLauncher.initCommandContext(CliLauncher.java:229)
at org.jboss.as.cli.impl.CliLauncher.main(CliLauncher.java:207)
at org.jboss.as.cli.CommandLineMain.main(CommandLineMain.java:34)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.jboss.modules.Module.run(Module.java:260)
at org.jboss.modules.Main.main(Main.java:291)
Caused by: org.jboss.as.cli.CommandLineException: The controller is not available at localhost:9999
at org.jboss.as.cli.impl.CommandContextImpl.connectController(CommandContextImpl.java:639)
at org.jboss.as.cli.impl.CommandContextImpl.connectController(CommandContextImpl.java:613)
at org.jboss.as.cli.impl.CliLauncher.initCommandContext(CliLauncher.java:227)
... 8 more

It turns out this is bug on the JDK 1.7 that only appears on MacOSX JDK7! To be exact, I have the following: MacOSX 10.7.4 with JDK 1.7.0_05 and JBossAS 7.1.1.Final.

This problem has already been discovered by these posts:

Except after ready through all, and I can't still get it to work because no one listed a clear steps by steps solution. So I re-summarize it again. You need to use that bug's report workaround solution on both the server and client!

Start the server: 

$ bin/standalone.sh -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.KQueueSelectorProvider 
... 
18:37:22,555 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: JBoss AS 7.1.1.Final "Brontes" started in 1686ms - Started 133 of 208 services (74 services are passive or on-demand)

And on separate terminal, run the CLI:

$JAVA_OPTS="-Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.KQueueSelectorProvider" bin/jboss-cli.sh --connect 
[standalone@localhost:9999 /] 

There, now we may continue to explore the cool JBossAS on the fancy OS.

Sunday, July 22, 2012

Script Console Tool for any WAR application

In the latest release of TimeMachine scheduler project, I've added a Groovy scripting console to the web app and allow user to script the scheduler. This feature is actually very useful for any servlet application as well. Imagine that if you have a script/shell console for your web application that allow you to dynamically inspect any variables or data?

With the same idea, I've written a very basic JSP file script-console.jsp that provides a great ad hoc tool. For simplicity sake, I put everything into single file. All you need is just add this one file into your java webapp root directory, and you'll have an instant scripting console shell! (Yes, I am aware having code in JSP is bad, but having everything in one page is convenient, specially if you plan to just use this as one time inspection.)

The JSP will automatically detect all the scripting engine available in your JVM (1.6+) and let you choose any one to use. In the text area you can enter any script codes. All the JSP implicit variables are available for you to use as well. JVM 1.6 or higher will have at least JavaScript engine available, so you can use it immediately.

Be aware that this is a huge security risk since the script console not only expose your application, it also expose your entire JVM! It must be use with care, and if possible, it needs to be added as protected resources in your web application.

Monday, July 16, 2012

TimeMachine Scheduler 1.2.0 release is out!


I just released the timemachine-scheudler-1.2.0, and it now comes with both stand-alone server (zip) and web application (war) distribution packages.

In this release, I fixed few major bugs and made many small clean ups and minor fixes. I also added few new features. I will briefly introduce them to you here.

Groovy 2.0

I have upgraded the optional script engine dependency to latest groovy 2.0.0 within our scheduler distribution. The groovy-2.0.0 has modularized their jar distribution, and our scheduler only requires minimal of jsr-223 module, so our overall package size should be smaller.

TimeMachine-Web

I have added a web application that can provide a fully running scheduler. This web app provides UI screens that let you manage and view jobs/schedules, and it can even edit the scheduler configuration online.

You can see more info on WebConsole doc, or try it out with our demo.

Schedule Priority

You may now set a priority value 1-9 on any Schedule, and the default is 5. These are used if you have schedules that has same nextRun time, and having the lower priority value will get to run first.

Event and Job Histories

In this release, I also provided a EventHistoryService that can record all major scheduler events such as when scheduler init, start, stop or destroyed. Also when scheduling data are added or deleted. And all the job run events are also recorded. To support this, we added a new built-in data model: EventHistory. You may retrieve and store these with our DataStore service if you desire. As convenience, we have added a History screen on our web application that display all the events happening in the scheduler.

This feature is disabled by default, but to enable it is easy. You simply add the following value in the scheduler configuration file:
timemachine.scheduler.eventHistory.class = timemachine.scheduler.service.EventHistoryService

There are few more options you may configure with this service, and you can read more it on the scheduler reference doc.

Friday, July 13, 2012

CTRL+C not working on Cygwin 1.7.15-1

When I installed new package in my Cygwin recently, it also automatically upgraded to latest Cygwin 1.7.15-1 DLL. After this, pressing CTRL+C no longer work
on a running Java process the cygwin shell. I found the reason why this happens, and here is the email I sent to cygwin mailing list:
Okay, I finally found out what's going on.

I used to have an old cygwin installed (not even sure what version) that only has "C:\Cygwin\Cygwin.bat" to start an terminal. This batch file open a terminal that I can run java.exe, and I used to hit CTRL+C to end it (not only that, it will also invoke the Java's shutdown hook.)

After I upgraded to cygwin 1.7.15-1 (it will auto upgrade when we run setup.exe!). The above behavior no longer works!

It turns out the new cygwin 1.7.15-1 automatically comes with Mintty terminal now, and will default to create a Shortcut to this on desktop. Well I still have a shortcut to "C:\Cygwin\Cygwin.bat". What I discover is that Java will no longer work with terminal that opens with "C:\Cygwin\Cygwin.bat"! But it DOES work with the Mintty terminal!

It's all great for me, because I kind of like Mintty terminal. It's kindda funny because for years I would love to use Mintty, but only to stop because CTRL+C wont' work there. Now we have reverse!

However, I have to point out also that although I can hit CTRL+C in mintty to kill a java.exe process, but it DOES NOT invoke the Java's shutdown hook process! Which is shame, because now I can't test my shutdown procedure code.

I hope cygwin team can look at this further and provide a good solution, even for the Java folks like myself. I can only cope with Windows because of cygwin exists, so kudo to all the cygwin team and their hard work!

Hope also this post will help other Java developers out there.

Cheers,
Zemian
Ref: http://old.nabble.com/CTRL%2BC-is-not-working-with-java-on-latest-cygwin-1.7.15-tt34147441.html

Thursday, July 12, 2012

Quick way to benchmark Java code

Don't you envy Ruby has the Benchmark module? Yes, there are many art and science go behind how to setup and run a good micro benchmark code, especially with the JVM. But many times you just want to see some result, quickly. There is actually a very groovy and quick way to bench mark Java code with gbench! Check this out:

 
@Grab('com.googlecode.gbench:gbench:0.3.1-groovy-2.0')
@Grab('commons-lang:commons-lang:2.6')
import gbench.*
import org.apache.commons.lang.StringUtils
new BenchmarkBuilder().run {
    'jdk.String.split' {
        100.times{ "a b c d e f g h i j k l m n o p q r s t u v w x y z".split(" ") }
    }
    'commons-lang.StringUtils.split' {
        100.times{ StringUtils.split("a b c d e f g h i j k l m n o p q r s t u v w x y z", " ") }
    }
}.prettyPrint()

I ran above and get the following on my machine:

$ groovy benchmarkSplit.groovy
Environment
===========
* Groovy: 2.0.0
* JVM: Java HotSpot(TM) Client VM (20.1-b02, Sun Microsystems Inc.)
    * JRE: 1.6.0_26
    * Total Memory: 15.5 MB
    * Maximum Memory: 247.5 MB
* OS: Windows XP (5.1, x86)

Options
=======
* Warm Up: Auto
* CPU Time Measurement: On

                                  user  system     cpu    real

jdk.String.split                663219       0  663219  693382
commons-lang.StringUtils.split  192721       0  192721  212359


Wednesday, July 11, 2012

Minimal Java webapp with Maven

Below are the shortest steps I know that would get you the smallest Java web application ready in Maven.

    $ mkdir -p webapp/src/main/webapp/WEB-INF/classes
    $ echo '<web-app></web-app>' > webapp/src/main/webapp/WEB-INF/web.xml
    $ echo '<html>Hello World.</html>' > webapp/src/main/webapp/index.jsp
    $ echo '<project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>atest</groupId>
        <artifactId>webapp</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    </project>' > webapp/pom.xml
    $ cd webapp
    $ mvn org.apache.tomcat.maven:tomcat7-maven-plugin:2.0-beta-1:run


You should able to visit http://localhost:8080/webapp after that. Editing any JSP files should auto refresh by the server, and you don't need to restart it. This is a fast way to prototype and test out your ideas.

(Yes, I am aware that maven has the archetype for webapp, but it above do give you the bare minimal and you see each lines what go into your project.)

Tuesday, July 10, 2012

Kickstart TimeMachine Scheduler with a quick Groovy script

If you have Groovy installed, you can kick start a TimeMachine Scheduler job without any setup! Try this out:


// Create a repeating schedule job that runs every 3 seconds.
@Grab('org.slf4j:slf4j-simple:1.6.6')
@Grab('org.bitbucket.timemachine:timemachine-scheduler:1.2.2')
import timemachine.scheduler.*
scheduler = new SchedulerFactory().createScheduler()
scheduler.init()
jobDef = JobDefs.groovyJobDef('''
    println("Hello World.")
''')
jobDef.addSchedule(Schedules.secondly(3))
scheduler.schedule(jobDef)
scheduler.start()
addShutdownHook{ scheduler.destroy() }


Wednesday, July 4, 2012

Getting JDK6 src.zip for new iMac

I got a new iMac, and I have activated the first time usage of JDK6. It's all working, but it doesn't come with src.zip! I googled around but other's solution didn't work for me. (eg: http://stackoverflow.com/questions/4011002/java-eclipse-on-macosx-where-is-the-src-zip). I tried download their Apple JDK updates, but it still doesn't have the src.zip.


So finally realized that this is easier than it needs to be. You can download the full JDK source here: 
http://download.java.net/openjdk/jdk6

After unzip, you should see the src folder under like this:
openjdk-6-src-b25-01_may_2012/jdk/src/share/classes

I've set this in my Eclipse's classes.jar as source folder and it works great.

PS: If you install the JDK7 from Oracle for MacOSX, it does come with the src.zip properly. However if you use Eclipse IDE with maven that set target to 1.6, it still convenient to browse that version of the source.