7/28/2006

How to Set Ant Options in NetBeans

Sometimes I need to ask NetBeans to run ant with -verbose or -debug options, when building or running a project. It can be set at Tools | Options | Misc | Ant Options

This took me a while to find out. I first thought it must be somewhere in the project property, similar to how I set java compiler option and java runtime options.

As it turns out, ant options in NetBeans are global, and any changes will affect all projects. I'm wondering if it makes sense to allow it to be set at project level as well, with project-wide setting overriding IDE-wide setting.

Why I don't Need toplink-essentials.jar in classpath

I use TopLink bundled inside Glassfish. Compiling and running java persistence application is easy, even when done from command line, as you can see from the previous post.

At compile time, I only have toplink-essentials-agent.jar in classpath. This jar only contains a java agent class:
oracle/toplink/essentials/internal/ejb/cmp3/JavaSECMPInitializerAgent.clasBut its META-INF/MANIFEST.MF contains a Class-Path entry , in addition to a Premain-Class entry:

Premain-Class: oracle.toplink.essentials.internal.ejb.cmp3.JavaSECMPIn
itializerAgent
Class-Path: toplink-essentials.jar
<other entries omitted here>
So the class loader will follow this reference and load toplink-essentials.jar in the same directory. toplink-essentials.jar contains all the Java Persistence API and implementation classes.

At runtime, I don't even have any toplink jar in classpath. Because we use -javaagent:toplink-essentials-agent.jar, and any agent jars are automatically appended to the classpath. I wrote this post on -javaagent: option. So runtime classpath only needs to include application classes and jdbc driver jar.

In NetBeans 5.5, I found I do need to include both toplink-essentials-agent.jar and toplink-essentials.jar in project library. Otherwise, NetBeans can't find JPA classes.

HelloWorld with JPA, Hibernate and MySql

An earlier entry shows how to write a simple java application using Java Persistence API (JPA), Toplink and MySql. Now let's try Hibernate Entity Manager.

Entity class and main class remain the same. persistence.xml is different since we will need to specify Hibernate-specific properties:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="hello-world" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.foo.Greeting</class>
<properties>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/test"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.password" value=""/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
Compile classpath is different, since we need to use Hibernate jars. Strictly speaking, javac only needs JPA API classes in the classpath. But just to be consistent with runtime, it does't hurt to include all 17 Hibernate jar files.

To save some typing, I decided to use * wildcard in classpath, a new feature in JDK 6 (see my previous post):
C:\ws\nb\scrap\src\com\foo> javac
-d C:\ws\nb\scrap\build\classes
-classpath "C:\tools\hibernate-em\*"
Greeting.java HelloWorld.java
Note that the "" around the classpath element is needed. Otherwise, you will get javac error: invalid flag.

You also need to have a log4j.properties file in classpath. I have it in build/classes/log4j.properties:
log4j.rootCategory=WARN, dest1
log4j.appender.dest1=org.apache.log4j.ConsoleAppender
log4j.appender.dest1.layout=org.apache.log4j.PatternLayout
Unlike TopLink, Hibernate doesn't use -javaagent: option. First start MySql on localhost, then
C:\ws\nb\scrap\build\classes> java -cp
C:\tools\hibernate-em\*;C:\tools\mysql-java\mysql-connector-java-3.1.13-bin.jar;.
com.foo.HelloWorld
createDDL.jdbcO-:-Ofalse
dropDDL.jdbcO-:-Ofalse
log4j.propertiesO-:-Ofalse
META-INF/persistence.xmlO-:-Ofalse
No configuration found. Configuring ehcache from ehcache-failsafe.xml found in
the classpath: jar:file:/C:/tools/hibernate-em/ehcache-1.2.jar!/ehcache-failsafe.xml
Query returned: Greeting id=1, message=hello world, language=en
The 17 jars used by Hibernate Entity Manager are:
antlr-2.7.6.jar, commons-collections-2.1.1.jar, hibernate-annotations.jar, jta.jar, asm-attrs.jar, commons-logging-1.0.4.jar, hibernate-entitymanager.jar, log4j-1.2.11.jar, asm.jar, dom4j-1.6.1.jar, hibernate3.jar, c3p0-0.9.0.jar, ehcache-1.2.jar, javassist.jar, cglib-2.1.3.jar, ejb3-persistence.jar, jboss-archive-browsing.jar


This blog talks about doing all these in NetBeans 5.5 beta 2, very helpful.

JDK 6 Supports * in Classpath But Be Aware of This

Finally JDK 6 (Mustang) lets me use wildcard (*) in classpath. I no longer have to enumerate all jar files in a lib directory. But with this added feature, it's easier to get errors like "javac: invalid flag:" For example, I want to complile a simple servlet, including all jar files in C:\Sun\AppServer\lib:

C:\tmp> javac -cp C:\Sun\AppServer\lib\*
HelloWorldServlet.java
javac: invalid flag: C:\Sun\AppServer\lib\addons
Usage: javac <options> <source>
use -help for a list of possible options
What's happening here is, the OS expands * to all files/directories in C:\Sun\AppServer\lib, since * has special meaning for the OS. So the real command resolves to:
javac -cp C:\Sun\AppServer\lib\activation.jar
C:\Sun\AppServer\lib\addon
C:\Sun\AppServer\lib\admin-cli.jar
C:\Sun\AppServer\lib\ant ... HelloWorldServlet.java
As all files/directories are expanded alphabetically, the first one (activation.jar) is treated as classpath element, and everything else starting with addon are treated as separate JVM options. Of course these are invalid JVM options, hence javac: invalid flag:

Note: if you are using javaw.exe instead of java.exe, * wildcard expansion in -cp or -classpath doesn't work, due to JDK 6 bug 6510337

So here are some tricks how to use * in classpath without getting burned:
  • "*" and "*.jar" have the same effect when used in classpath.
  • Quote it, if your classpath has a single element that uses *. For example,
    javac -cp "C:\Sun\AppServer\lib\*" HelloWorldServlet.java
  • This problem doesn't exist if you have more than one element in classpath, even though they do not exist. When in doubt, always quote your classpath value that uses wildcard.
    javac -cp C:\Sun\AppServer\lib\*;\nosuchdir HelloWorldServlet.java
  • This problem exists for all JDK tools that takes classpath option, including java. The error is different but solution is the same as above.
    java -cp C:\temp\* A
    Exception in thread "main" java.lang.NoClassDefFoundError: C:\temp\b/jar
  • Of course, if the directory contains 1 jar file, it will be expanded correctly, and you don't need to quote it. But using * doesn't save you much typing:
    java -cp C:\single-jar-file-folder\* A
    this is A
  • If on Unix, whether to quote or not quote wildcard classpath depends on which shell you are using. For example, tcsh or csh may fail with the following error: "java: No match." But the exact same command works under bash:
    java -cp tmp:$HOME/modules/*.jar Test abc

7/27/2006

HelloWorld with JPA, TopLink and MySql

This is a simple example of standalone java application using Java Persistence API (JPA), TopLink persistence engine, and MySql. It consists of an entity class, a main class, and a persistence.xml file. No need to create tables, as they are created and dropped automatically.

META-INF/persistence.xml must be at the root of the persistence unit. In my example project, it's C:\ws\nb\scrap\build\classes\META-INF\persistence.xml.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="hello-world" transaction-type="RESOURCE_LOCAL">

<!-- comment out to use the default provider
<provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
-->

<class>com.foo.Greeting</class>
<properties>
<property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/test"/>
<property name="toplink.jdbc.user" value="root"/>
<property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="toplink.jdbc.password" value=""/>
<property name="toplink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
Entity class:
package com.foo;
import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Greeting implements Serializable {
@Id @GeneratedValue private int id;
@Basic private String message;
@Basic private String language;

public Greeting() {}
public Greeting(String message, String language) {
this.message = message;
this.language = language;
}

public String toString() {
return "Greeting id=" + id + ", message=" + message + ", language=" + language;
}
}
Main class:
package com.foo;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class HelloWorld {
private EntityManagerFactory emf;
private EntityManager em;
private String PERSISTENCE_UNIT_NAME = "hello-world";

public static void main(String[] args) {
HelloWorld hello = new HelloWorld();
hello.initEntityManager();
hello.create();
hello.read();
hello.closeEntityManager();
}

private void initEntityManager() {
emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
em = emf.createEntityManager();
}

private void closeEntityManager() {
em.close();
emf.close();
}

private void create() {
em.getTransaction().begin();
Greeting g_en = new Greeting("hello world", "en");
Greeting g_es = new Greeting("hola, mundo", "es");
Greeting[] greetings = new Greeting[]{g_en, g_es};
for(Greeting g : greetings) {
em.persist(g);
}
em.getTransaction().commit();
}

private void read() {
Greeting g = (Greeting) em.createQuery(
"select g from Greeting g where g.language = :language")
.setParameter("language", "en").getSingleResult();
System.out.println("Query returned: " + g);
}
}
To compile the project,
C:\ws\nb\scrap\src\com\foo>
javac -d C:\ws\nb\scrap\build\classes
-classpath C:\Sun\AppServer\lib\toplink-essentials-agent.jar
Greeting.java HelloWorld.java
To run it, start MySql on localhost, and run
C:\ws\nb\scrap\build\classes>
java -javaagent:C:\Sun\AppServer\lib\toplink-essentials-agent.jar
-cp .;C:\tools\mysql-java\mysql-connector-java-3.1.13-bin.jar
com.foo.HelloWorld

[TopLink Info]: 2006.07.27 01:34:26.484--ServerSession(20003078)--TopLink, version: Oracle TopLink Essentials - 2006.4 (Build 060412)
[TopLink Info]: 2006.07.27 01:34:27.686--ServerSession(20003078)--file:/C:/ws/nb/scrap/build/classes-hello-world login successful
[TopLink Warning]: 2006.07.27 01:34:27.926--ServerSession(20003078)--Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2006.4 (Build 060412)): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Table 'sequence' already existsError Code: 1050
Call:CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
Query:DataModifyQuery()
Query returned: Greeting id=1, message=hello world, language=en
[TopLink Info]: 2006.07.27 01:34:28.447--ServerSession(20003078)--file:/C:/ws/nb/scrap/build/classes-hello-world logout successful

7/24/2006

The -javaagent: Option

The -javaagent: is introduced in JDK 5, and it may be late to talk about any new features in JDK 5, while JDK 6 is just around the corner. I started to use it recently but at first couldn't find any good documentation on this option.

java -help shows a brief message:

-javaagent:<jarpath>[=<options>]
load Java programming language agent, see java.lang.instrument
JDK tools doc page doesn't give much more info. The official one is at the Javadoc page for java.lang.instrument, as suggested by java -help

Here is my quick summary with comments:

1. An agent is just an interceptor in front of your main method, executed in the same JVM and loaded by the same system classloader, and governed by the same security policy and context.

The name is misleading, since the word agent usually suggests something working remotely and separately from the primary entity. But it turns out the java agent as used in -javaagent: is much simpler than that.

How to write a java agent? Just implement this method:
public static void premain(String agentArgs, Instrumentation inst);
2. Agent classes must be packaged in jar file format whose META-INF/MANIFEST.MF contains at least one additional attribute: Premain-Class. An example of MANIFEST.MF:
Manifest-Version: 1.0
Premain-Class: javahowto.JavaAgent
Created-By: 1.6.0_06 (Sun Microsystems Inc.)
Once you have the custom MANIFEST.MF file, run jar command with cvfm option to create the agent jar:
/projects/Hello/build/classes $
jar cvfm ../../myagent.jar ../../mymanifest.mf javahowto/MyAgent.class
3. All these agent jars are automatically appended to the classpath. So no need to add them to classpath, unless you want to reorder classpath elements.

4. One java application may have any number of agents by using -javaagent: option any number of times. Agents are invoked in the same order as specified in options.

5. Each agent may also take String-valued args. I guess that's the reason why we have to use this option multiple times for multiple agents. Otherwise, we could've just done something like: -javaagent agent1.jar:agent2.jar, which is incorrect.

6. It's convenient for java application integration. Now I can enhance/modify the behavior of an application without changing its source code.

7. JavaEE 5 has many similar construts, such as interceptors in EJB 3, and EntityListener in Java Persistence API. In JavaEE, they are managed by some sort of containers, so their semantics is much richer than javaagent.

7/22/2006

7 Things I don't Like in Co-workers' Computers

I shouldn't care about anything in my co-workers' computers. But once in a while, I need to sit down in front of their machines to help debug something, I find these things annoying and slowing things down. For me, it's just a few times, no big deal. But for owners of these machines, I don't know how many person-months are wasted on typing and keystrokes.

1. vim not in path, or not installed anywhere in the current machine. There may be some network locations with vim executable, but it's hard to remember where they are. So I have to use /bin/vi.

2. csh is used, rather than tcsh, bash, zsh, etc. If they really love csh, why can't they just upgrade to tcsh, which is totally csh-compatible. I guess /bin/csh is the default login shell and they never bother to change it.

3. Current path is not displayed as part of the prompt, and users are more likely to run commands in the wrong directories. I have set prompt="%/ > " in my $HOME/.tcsh, to always include `pwd` in the prompt. I don't remember the last time I run pwd command.

4. File/path-completion doesn't work, or not smart. For example, csh uses the hard-to-reach escape key for its limited file-completion, and doesn't support case-insensitive match, possible matches listing, nor hyphen-underscore auto switch. I usually don't type a complete path. I don't trust my typing, even though I'm a pretty good typist.

5. $JAVA_HOME is not defined, or $JAVA_HOME/bin is not in the path. As a result, commands like java, javac, jar, etc are not recognized.

6. Up and Down keys don't show command history. Without these two history keys, I will have to run history command all the time to get the previous commands. Slightly better, use shortcuts like !java, but that is not very accurate and may give you a javac -classpath ... command.

7. Shell background is not black or dark gray. It's just a matter of personal preference.

To alleviate some of these pains, I created a $HOME/.common file, and every time I go to another workstation, I just run tcsh and source /home/me/.common.

Tags: , , ,

7/18/2006

NullPointerException and autoboxing

I recently came across a NullPointerException that is not evident from the line number. To reduce it to a simple testcase:

package com.javahowto.test;

public class Project {
private Integer version;

public static void main(String[] args) {
Project project = new Project();
System.out.println(project.toString());
//NPE in the next line
int version = project.getVersion();
}

private Integer getVersion() {
return version;
}
}
In that line, it seems the only suspect is the variable project. But if project was null, the constructor, or project.toString() should've failed, and the execution shouldn't reach project.getVersion().

You may have noticed it's the autoboxing, the implicit cast from Integer to int that has caused NPE. This line
int version = project.getVersion();
is really this at runtime:
int version = project.getVersion().intValue();
project.getVersion() returned null and caused NPE. Unlike primitives, Integer and other wrapper types don't default to 0 when used as instance or class variables. With the autoboxing in JDK 5, primitives and their wrapper types are almost but not completely interchangeable.

7/15/2006

Don't Close Servlet OutputStream or PrintWriter

I don't think web app developers need to close ServletOutputStream or PrintWriter in servlet classes. They are created and managed by the web container, and web components should not interfere with its lifecycle. If you do close them inside your servlet service/doGet/doPost methods, it won't cause trouble for a fault-tolerant container.

When NetBeans generates a new servlet, it does close the PrintWriter in processRequest method:


protected void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
/* TODO output your page here
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet MyServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet MyServlet at "
+ request.getContextPath () + "</h1>");
out.println("</body>");
out.println("</html>");
*/
out.close();
}
If NetBeans insists on closing ServletOutputStream, the safe way is to close it in a finally block.

In short, don't close servlet OutputStream or PrintWriter, just as you won't close System.out.

7/08/2006

NetBeans Now Knows How to Indent Java Annotations

NetBeans 4 is the first java IDE to support JDK 1.5 language features, and it has been working for me very well. One little annoyance in NetBeans 4.1 and 5 is it doesn't know how to indent java annotations. For example, it would indent and format the following method like this:

@Override
public String toString() {
return super.toString();
}
After typing @Override and return, the cursor in the new line is already indented 8 spaces. I have to manually correct it. I have to remember not to press CTRL-SHIFT-F to format code, since it will ruin my carefully and manually formated code and format java annotations in its own unique way.

Finally, it was fixed in NetBeans 5.5 beta and formats it the way I (I guess most people) like:
@Override
public String toString() {
return super.toString();
}
This is also how jEdit and Eclipse 3.2 formats java annotations. Note that @Override doesn't need to be in its own line, but it looks cleaner to me.

7/07/2006

Java Annotations with No Target

Perhaps you would expect every Java annotation to have one or multiple targets, indicating where it can be applied, either on a class, method, field, or some combination. It's correct for the most part, but it also turns out some java annotations have no target. Their target() field is an empty array. For example,

  • javax.ejb.ActivationConfigProperty:

    @Target(value={})
    @Retention(value=RUNTIME)
    public @interface ActivationConfigProperty
  • javax.persistence.EntityResult

    @Target(value={})
    @Retention(value=RUNTIME)
    public @interface EntityResult
Where can we use these targetless annotations? They are to be used as nested annotations inside other enclosing annotations. Maybe we need a new target value NESTED? For example, @ActivationConfigProperty can only be used inside @javax.ejb.MessageDriven to specify properties for the Message-Driven Bean (MDB):
@MessageDriven(name="MessageBean",
activationConfig = {
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(propertyName="acknowledgeMode",
propertyValue="Auto-acknowledge"),
@ActivationConfigProperty(propertyName="subscriptionDurability",
propertyValue="Durable"),
@ActivationConfigProperty(propertyName="messageSelector",
propertyValue="name='java' AND bugs < 1")
})
public class MessageBean implements MessageListener {
...
}
@javax.persistence.EntityResult can only be used as nested annotation inside @javax.persistence.SqlResultSetMapping. Here is a more complex example from its java doc, with 3 nested annotations at 3 different levels:
@SqlResultSetMapping(name="OrderResults",
entities={
@EntityResult(entityClass=com.acme.Order.class, fields={
@FieldResult(name="id", column="order_id"),
@FieldResult(name="quantity", column="order_quantity"),
@FieldResult(name="item", column="order_item")})},
columns={
@ColumnResult(name="item_name")}
)

7/06/2006

To Use or Not to Use Cygwin

Yes, if you just want to use those neat Unix utilities, like grep, head, touch, tail, more, etc, and if you are patient enough to manage Cygwin installation. For anything beyond that, you should really consider Solaris x86 or Linux.

Cygwin installer is my worst install experience. After using Cygwin for years, I still find it confusing and tiresome to check/uncheck these little source/binary/remove icons.

I wouldn't run any serious apps, not even Apache Ant, from within a Cygwin shell. I don't need more dll mismatches, or path configuration problems. I don't want to duplicate all my environment variables (JAVA_HOME, ANT_HOME, MAVEN_HOME, CATALINA_HOME, JBOSS_HOME, SJSAS_HOME, etc) in both Windows Control Panel and .profile/.bash/.tcsh. I don't want to rewrite my convenience batch files to tcsh alias or bourne shell scripts.

I wouldn't build my projects using any part of Cywin. I wouldn't require team members to have Cygwin in their Windows box. Of course, I would never ask end users of my software to first install Cygwin, or some other Unix clone on Windows. If any software product have such requirement, I have doubt on its quality and maintainability.

If my development environment is Windows, I would hire a release engineer good at batch files, or even better, an Ant expert.

Something You May Not Know about Jar Command: Part 3

8. jar command options can start with optional -. jar tvf a.jar is the same as jar -tvf a.jar

9. You would usually use relative paths for source files, relative to the current directory. These relative paths will be preserved inside the target jar file. For example, jar cvf \tmp\b.jar com\javahowto\test\ will create the directory tree com\javahowto\test\ inside the target jar, and include all files under <current-dir>\com\javahowto\test\.

If you use absolute paths for source files, jar will copy the absolute paths inside the jar file, which is not what you want. For example,

C:\tmp > jar cvf a.jar C:\tmp\A.class
added manifest
adding: C:/tmp/A.class(in = 405) (out= 279)(deflated 31%)
10. If source files are not located in the current directory, you can use -C option to tell jar command to implicitly change to another directory and then include files there. For example,
C:\ws\nb\scrap\dist > jar cvf hello-world.jar -C ..\build\classes com\javahowto\test
11. If you use -C ../build/classes option and want to include all files under ../build/classes, use . (dot) to represent all files there. Note that you can't use *, which will resolve by OS to all files to the current directory. For example,
C:\ws\nb\scrap\dist > jar cvf hello-world.jar -C ..\build\classes .
The following example (using -C and *) will ignore -C option and instead include all files in the current directory, which is not what we want:
C:\ws\nb\scrap\dist > jar cvf hello-world.jar -C ..\build\classes *
..\build\classes\com\hello-world.jar : no such file or directory
added manifest
adding: scrap.jar(in = 20487) (out= 7472)(deflated 63%)
12. There are 3 types of paths in jar command:
  • path to the destination jar file, either relative or absolute path is fine. In fact, any format is ok as long as it can be correctly resolved by the OS

  • path to the manifest file, if m option is present. Either relative or absolute path is fine. The same as destination file.

  • multiple paths to source files. They should be relative to the current directory, unless -C option is present. In that case, all source files should be relative to the value of -C option.
If you don't like using -C option, I'd suggest you always cd into the parent directory of all source files, and then run jar command.

13. It is not possible to include source files from multiple different parent directories. But you can always first copy them into a common parent directory. Jar task in Apache Ant is more flexible and can accommodate almost all use cases.

14. In JDK 6 or newer version, you can use e option to specify an entry-point class (Main-Class in META-INF/MANIFEST.MF) for self-contained applications packaged in a jar file. See this post for details.

7/05/2006

Something You May Not Know about Jar Command: Part 2

4. You can choose not to have manifest file when creating a jar file, using M option:

jar cvfM no-meta.jar A.class
adding: A.class(in = 405) (out= 279)(deflated 31%)
Note: it's upper-case M. Lower-case m has a different meaning.

5. You can specify your own manifest file when creating a jar file, using m option:
jar cvfm my-meta.jar my-meta-inf\my.mf A.class
added manifest
adding: A.class(in = 405) (out= 279)(deflated 31%)
The option used here cvfm tells the jar command that the destination file (f) will come next and then custom manifest file (m). I can also specify them in a different order:
jar cvmf my-meta-inf\my.mf my-meta.jar A.class
added manifest
adding: A.class(in = 405) (out= 279)(deflated 31%)
It's lower-case m. Upper-case M has a different meaning.

6.META-INF/MANIFEST.MF file in source files is always ignored.
jar cvf ignore.jar A.class META-INF
added manifest
adding: A.class(in = 405) (out= 279)(deflated 31%)
ignoring entry META-INF/
adding: META-INF/LICENSE.txt(in = 2657) (out= 1185)(deflated 55%)
ignoring entry META-INF/MANIFEST.MF
When it comes to manifest files for new jar file, you only have 3 options:
  • Do not specify any manifest-related options and use the default MANIFEST.MF

  • Use option M not to include a manifest file

  • Use option m to use a custom manifest file. Inside the jar file, this file will always be named MANIFEST.MF under META-INF directory. Outside of the jar file, this custom manifest file can be anywhere and have any name.
7. If you unjar an archive, do nothing, and then jar it up again, the resulted new archive may not equal to the original one. And applications using this new archive may not work correctly because of the wrong manifest file. The original jar may have a custom manifest file, which is expaned into META-INF/MANIFEST.MF. But when you jar these files up again without using cvfm option, this custom manifest file is ignored and a default MANIFEST.MF is included.

Something You May Not Know about Jar Command: Part 1

1. jar command can also operate on zip files. I usually run jar tvf hello.zip to quickly view its content, without starting up the WinZip program. jar xvf hello.zip should also be able to expand the target zip files. I find it hard to memorize Unix zip/unzip command line options, so I just use jar tvf/jar xvf instead. For example:

C:\tmp > jar tvf eclipse-SDK-3.2RC7-win32.zip
2. jar tvf can selectively list table of contents for archive. I used to run jar tvf j2ee.jar | grep javax/servlet/http to search for servlet classes in j2ee.jar. Replace grep with findstr on Windows. In fact, I don't need grep or findstr; I can just run this command:
jar tvf j2ee.jar javax/servlet/http
Note that the search criteria are matched against the beginning of all entries in jar file. It uses String.startsWith(what) rather than String.contains(what). So this command jar tvf j2ee.jar ejb will not return any matching entries, though jar tvf j2ee.jar javax/ejb will return all ejb classes. The search is also case-sensitive.

If you want case-insensitive search, or match by any parts (not just the beginning) of entries, you still need to use jar tvf my.jar | grep -i aNynAmE

3. You can extract selected entries from a jar file. For instance, if you only want to view the meta-inf/manifest.mf file, you can
C:\Sun\AppServer\lib > jar xvf j2ee.jar META-INF/MANIFEST.MF
inflated: META-INF/MANIFEST.MF
Or using a backslash instead of a forward slash:
C:\Sun\AppServer\lib > jar xvf j2ee.jar META-INF\MANIFEST.MF
inflated: META-INF/MANIFEST.MF
The entry names are case sensitive, and so the following will not extract anything:
C:\Sun\AppServer\lib > jar xvf j2ee.jar meta-inf/manifest.mf
Of course, you can always double-click the entry to view it in WinZip, fileroller, or other tools.

A New Option in Jar Command in JDK 6

JDK 6 adds a new feature to the jar command: e. Other options of the jar command are still the same. This is the partial usage from JDK 6 beta 2:

C:\tools\jdk6\bin > jar
Usage: jar {ctxui}[vfm0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
Options:
...
-e specify application entry point for stand-alone application
bundled into an executable jar file
...
For example, I run jar command to package a self-contained application in a jar file:
C:\ws\nb\scrap\build\classes > C:\tools\jdk6\bin\jar cvfe  ..\..\dist\hello-world.jar com.javahowto.test.HelloWorld com
added manifest
adding: com/(in = 0) (out= 0)(stored 0%)
adding: com/javahowto/(in = 0) (out= 0)(stored 0%)
adding: com/javahowto/test/(in = 0) (out= 0)(stored 0%)
adding: com/javahowto/test/HelloWorld.class(in = 572) (out= 348)(deflated 39%)
Then I can distribute hello-world.jar to users, who can run hello-world app like this:
C:\download\hello > java -jar hello-world.jar
Hello world!
Users can run it with any version of java; it doesn't have to be JDK 6. What the extra e option does is simply adding a Main-Class entry in the jar's META-INF\MANIFEST.MF:
Manifest-Version: 1.0
Created-By: 1.6.0-beta2 (Sun Microsystems Inc.)
Main-Class: com.javahowto.test.HelloWorld
This new feature is helpful when packaging simple self-contained apps, and application client modules in J2EE/JavaEE, both of them require a Main-Class entry in MANIFEST.MF. So I don't have to create one beforehand, or have Apache Ant generate one.

7/04/2006

When and Where not to Use java:comp/env

In this post, I wrote about the need to use the prefix java:comp/env when looking up ejb/resource in the current naming environment. Equally important is to know where/when not to use it.

1. Don't use java:comp/env in standard deployment descriptors, such as web.xml, ejb-jar.xml, and application-client.xml. I can't think of any elements in these descriptors that contain java:comp/env.

2. Don't use java:comp/env in appserver-specific deployment plan files.

3. Don't use java:comp/env in any fields of resource and/or ejb injection, whether it's field or method injection.

4. Don't use java:comp/env when looking up resource/ejb using javax.ejb.EJBContext.lookup(String name). This is a new method in javax.ejb.EJBContext in EJB 3. The lookup name in this case is always relative to java:comp/env. For more details, please see 5 Ways to Get Resources in EJB 3.

5. Don't use java:comp/env when looking up certain standard J2EE and JavaEE resources. They reside directly under java:comp/, with no /env. For instance:

  • java:comp/UserTransaction

  • java:comp/EJBContext

  • java:comp/ORB

  • java:comp/TransactionSynchronizationRegistry
6. Don't use java:comp/env when looking up global resources in a server-dependent way. Some application servers let you look up resources by their global JNDI name. In such case, their lookup name should not contain java:comp/env. For example, java:/defaultDS in jboss. In JavaEE SDK 5/Glassfish/Sun Java System Application Server 9, I can also directly look up jdbc/__default (the default datasource) without configuring it in any descriptors. Note that this style of lookup is not portable. It ties your apps to specifc runtime server environment, and should really be avoided.

7. Don't use java:comp/env in the name of any physical resources inside an application server, like jdbc-resource, jdbc-pool, JMS queue or topic, EJB JNDI name, persistence manager, etc.

7/03/2006

Fix NameNotFoundException: Wrong Mapping in Deployment Plan

In previous posts, I wrote about two common causes of javax.naming.NameNotFoundException: incorrect lookup name and reference not declared. This post covers a third cause: wrong mapping of EJB/resource in appserver-specific deployment plans.

This sample project consists of a simple EJB3 stateless session bean HelloEJBBean, and an application client with main class hello.Main. A reference to HelloEJBBean's remote business interface is injected into the client main class.

package hello.ejb;
import javax.ejb.Remote;

@Remote
public interface HelloEJBRemote {
void hello();
}

package hello.ejb;
import javax.ejb.Stateless;

@Stateless
public class HelloEJBBean implements HelloEJBRemote {
public void hello() { }
}
Application client main class:
package hello;
import hello.ejb.HelloEJBRemote;
import javax.ejb.EJB;

public class Main {
@EJB(beanName="HelloEJBBean")
private static HelloEJBRemote helloEJB;

public static void main(String[] args) {
helloEJB.hello();
}
}
For the above sample app to work, we do not need any deployment descriptors or deployment plan. Why? because all metadata have been provided with annotations, or have defaults, or can be figured out by appserver one way or another.

But some IDEs still generate unnecessary deployment descriptors and deployment plans, which may have wrong mapping info. This happens without you knowing it. If you delete these unnecessary and wrong deployment plans, the next time you rebuild project, they will be regenerated.

For instance, NetBeans 5.5 beta generates the following sun-application-client-jar.xml, which is unnecessary and contains the wrong mapping data:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-application-client PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Application Client 5.0//EN"
"http://www.sun.com/software/appserver/dtds/sun-application-client_5_0-0.dtd">
<sun-application-client>
<ejb-ref>
<ejb-ref-name>helloEJB</ejb-ref-name>
<jndi-name>ejb/helloEJB</jndi-name>
</ejb-ref>
</sun-application-client>
Both the ejb reference name and ejb JNDI name are wrong. If the IDE really thinks a sun-application-client.xml is helpful for whatever reason, the ejb-ref element should be:
<ejb-ref>
<ejb-ref-name>hello.Main/helloEJB</ejb-ref-name>
<jndi-name>hello.ejb.HelloEJBRemote</jndi-name>
</ejb-ref>
because the default ejb reference name for an injected ejb is the of the format: <fully-qualified-class-name of the injection target class>/field-name. The default ejb JNDI name depends on appserver implementation, and for EJB3 in JavaEE SDK 5/Glassfish/Sun Java System Application Server, it's the fully qualified class name of the remote business interface.

IDE's best effort to generate deployment artifacts is still not good enough. With wrong mapping info in deployment plan, your app may fail to deploy, if the appserver validates the ejb reference at deployment time. Or even if it is deployed, it will fail at request time.

All posts in this series for NameNotFoundException:

Fix NameNotFoundException: Incorrect Lookup Name

Fix NameNotFoundException: Reference Not Declared

Fix NameNotFoundException: Wrong Mapping in Deployment Plan


Tags: , , , , , , , , , , , ,

7/02/2006

Fix NameNotFoundException: Reference Not Declared

J2EE 1.4 and earlier versions require all resource and ejb references be declared in the standard deployment descriptors, such as ejb-jar.xml, web.xml, and application-client.xml. These elements -- resource-ref, ejb-ref, ejb-local-ref -- register entries in the current component's private environment context. These declarations are needed, even when the reference name is the same as the global JNDI of the target resource or EJB.

For example, HelloServlet looks up the default DataSource in JavaEE SDK 5, whose global JNDI name is jdbc/__default:

public class HelloServlet extends HttpServlet {
protected void processRequest(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = null;
try {
out = response.getWriter();
Context ic = new InitialContext();
DataSource dataSource = (DataSource) ic.lookup("java:comp/env/jdbc/__default");
Connection connection = dataSource.getConnection();
out.println("Successfully looked up the default datasource: " + dataSource +
", and got the connection: " + connection);
} catch (NamingException e) {
throw new ServletException(e);
} catch (SQLException e) {
throw new ServletException(e);
}
}
Any portable applications must also declare a resource-ref in web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.foo.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
<resource-ref>
<res-ref-name>jdbc/__default</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
</web-app>
Without this resource-ref element in web.xml, the app fails with this error in JavaEE SDK 5/Glassfish/Sun Java System Application Server:

javax.naming.NameNotFoundException: No object bound to name java:comp/env/jdbc/__default
at com.sun.enterprise.naming.NamingManagerImpl.lookup(NamingManagerImpl.java:751)
at com.sun.enterprise.naming.java.javaURLContext.lookup(javaURLContext.java:156)
at com.sun.enterprise.naming.SerialContext.lookup(SerialContext.java:307)
at javax.naming.InitialContext.lookup(InitialContext.java:351)
at com.foo.servlet.HelloServlet.processRequest(HelloServlet.java:30)
We are not done yet. We also need to map the resource reference name (jdbc/__default) to the global JNDI name of the target resource (jdbc/__default). This step is appserver-dependent, usually editing an appserver-specific deployment plan file. For example, sun-ejb-jar.xml, sun-web.xml, sun-application-client.xml, or jboss.xml. The following shows how this is mapped in sun-web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">
<sun-web-app error-url="">
<context-root>/webapp14</context-root>
<resource-ref>
<res-ref-name>jdbc/__default</res-ref-name>
<jndi-name>jdbc/__default</jndi-name>
</resource-ref>
<class-loader delegate="true"/>
</sun-web-app>
When the local reference name and the global JNDI name are the same, some application servers may also have some default mapping rules to eliminate this step. For instance, in JavaEE SDK 5 and Sun Java System Application Server 9, this step is not needed when the two names are the same.

JavaEE 5 has introduced a series of annotations to greatly simplify configurations, such as @EJB, @EJBs, @Resource and @Resources. Using resource injections, applications no longer need to use ejb-ref, ejb-local-ref, nor resource-ref, and they may get rid of standard deployment descriptors altogether. But some sort of mapping mechanisms (default mapping rules, or appserver-specific deployment plans) are still needed.

More info on how to inject/lookup resources in JavaEE 5 and EJB 3, please refer to 5 Ways to Get Resources in EJB 3

All posts in this series for NameNotFoundException:

Fix NameNotFoundException: Incorrect Lookup Name

Fix NameNotFoundException: Reference Not Declared

Fix NameNotFoundException: Wrong Mapping in Deployment Plan