home | tech | misc | code | bookmarks (broken) | contact | README


Java notes

Last update to this page ocurred on 2017-11-20.

Developing a Java EE application without an IDE

With Java popularity, Java IDEs also arose as de facto standards for Java development. NetBeans and Eclipse emerged as the most known IDEs in the Java world and later have added support for other languages.

Companies have standardized those (or other) IDEs as the unique development environment developers could use, to the detriment of other tools like simple text editors (Vim or Emacs) and, sometimes, have prohibit developers to use tools other than the IDE they have chosen. IDEs also provide a tight integration with other tools (compilers, deploy tools, source code review, etc.) that makes it difficult for text editors users to know how to to integrate than besides the traditional way (editor + Makefile).

The idea of this section is to document my experience regarding using a project developed in Eclipse using nothing more than Vim and command line tools to build and making a full deploy of a Java EE (.ear) application. I believe that the procedure may be similar for NetBeans or other IDEs.

The scenario

My scenario is: I started to work in a relatively small team with around 8 developers developing a Java EE application with around 1 million of lines of code. The code base was bought from another company and all of it was developed from Eclipse. The (poor) documentation was fully written taking Eclipse as a starting point and all related tools are integrated with Eclipse. Want to generate a simple EAR file? You need Eclipse. Want to run JBoss to show up your application? You need to open Eclipse and launch JBoss from there.

The application we were developing was heavy. It spawns dozens of threads on startup, it used lots of different frameworks and it needs at least 4 GB to run basic features. Worse: JBoss and Java are itself bloated stuff that made things worse. How someone could still use Eclipse in this environment? We cannot get rid of the application or JBoss, but we can get rid of Eclipse.

To get things worse, all developers used either Microsoft Windows or Ubuntu GNu/Linux, which Eclipse supports, but I used NetBSD.

The procedure to eliminate Eclipse and use Apache Ant to compile the project

First of all, Eclipse does not use the javac compiler that you will find in a plain JDK installation. It uses its own compiler called Eclipse Compiler for Java (ECJ) and they do that because of tighter integration with the IDE (see this for more details).

For a reason I don't remember, I couldn't use the javac compiler that comes with JDK. So I had to use Eclipse compiler. Because of that, you will need to copy Eclipse plugins directory anywhere, where package org.clipse.jdt.core.* and dependencies belong to. To avoid dependency problems, I copied the full plugins directory. In this example, it was copied to /opt/eclipse-kepler-plugins (yes, it is a past version of Eclipse but it is what the team is using).

Eclipse also builds the project automatically, without a explicit build file (like a Makefile or Apache Ant build.xml). Since we will build the project manually after editing files in the text editor, we will need to generate this script. This is an Apache Ant script. To generate that, in Eclipse, click the File menu, and then Export. Chose option Ant build file and export it. It will generate a file build.xml.

Note

Since I didn't want to install Eclipse just to generate this file, I asked one of my colleagues to generate it for me.

Note

In the project we were working on, there was already a build.xml file used to generate EAR files. Because of that I exported to a different name, make.xml and had to explicitly pass its name to ant as ant -f make.xml.

So, after generating the build.xml file, you'll need to add a line to it, specifying that you are going to use the Eclipse compiler. According to this page you just need to include the following line just before the <javac ...> tag:

<property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter" />

An important thing is to add the JAR files of the eclipse plugins directory to your class path. It can be done within the build.xml file. In your project's <path ...> tag, add it:

<fileset dir="/opt/eclipse-kepler-plugins" includes="*.jar"/>

It may be enough! A call to ant should now compile every .java file and generate .class files. Minor tweaking may be necessary in build.xml to match your system configuration.

Note

How to generate .ear files is beyond the scope of this text, since there was a separated ant file in the project that did that and I just called it, never needing to see how it worked.

Deleting blank paragraphs when generating reports with XDocReport

XDocReport is a great solution developed by Angelo Zerr for those who want to escape from unflexible report solutions like Jasper Reports. XDocReports allows users to develop their own reports with LibreOffice or Microsoft Office using a Freemarker or Velocity template engines. The speciall advantage of it is that it doesn't require the user to have deep programming knowledge to develop the reports (of course the backend Java code might provide a user interface to allow users to easily plug in their reports to the solution).

A problem with XDocReport, though, is that some preprocessed lines are expanded to blank paragraphs (or blank lines). At the time I write this (although we are in 2017, we are using XDocReport 1.0.6 by 2016) , it is a feature request here and there have been discussed in this xdocreport Google Groups entry. It is not a easy problem to address without adding more syntax to the reports.

I decided to implement my own @removeLine feature based on the Google Groups discussion. The user have to add @removeLine string on every blank line or line that will expand to a blank line. I process the report passing a ByteArrayOutputStream as output:

ByteArrayOutputStream out = new ByteArrayOutputStream();
report.process(context, out);

Important

The Java code we show here is of version 1.6 in a legacy system, full of problems. I'd like to update and fix it, but unfortunatelly I don't have this time. Watch out for bad code!

Than, instead of writing the ByteArrayOutputStream to a file, obtaining the result in a ODT file (we are working with ODT here) we are going to manipulate it a little more:

ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
IOUtils.copy(in, out);
in.reset();

XDocArchive xdocarchive = XDocArchive.readZip(in);
xdocarchive = removeLines(xdocarchive);

HttpServletResponse r = getCurrentResponse();
ServletOutputStream outservlet = r.getOutputStream();
r.setContentType("application/odt");
r.addHeader("Content-Disposition", "attachment; filename=report.odt");

XDocArchive.writeZip(xdocarchive, outservlet);

We showed that we are sending the result HTTP but the import thing about this snippet is that we pass the xdocarchive object (of type XDocArchive) to the removeLines() and removeXMLNodes() methods, where important thing happens. Let's see how it works:

private static void removeXMLNodes(Node elem, String xpathstr) {
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xpath = xpf.newXPath();
        try {
                XPathExpression e = xpath.compile(xpathstr);
                NodeList nodes = (NodeList) e.evaluate(elem, XPathConstants.NODESET);
                for (int i = 0; i < nodes.getLength(); i++) {
                        Node node = nodes.item(i);
                        Node parent = node.getParentNode();
                        parent.removeChild(node);
                }
        } catch (Exception e) {
                e.printStackTrace();
        }
}

private XDocArchive removeLines(XDocArchive xdocarchive) throws IOException, SAXException, TransformerException, ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();

        /* Convert InputStream to ByteOutputStream */
        byte[] buf = new byte[8192];
        int bytesRead = 0;
        ByteArrayOutputStream out_barray = new ByteArrayOutputStream();
        InputStream in = xdocarchive.getEntryInputStream("content.xml");
        while ((bytesRead = in.read(buf)) != -1) {
                out_barray.write(buf, 0, bytesRead);
        }
        in.close();

        /* Now convert ByteArrayOutputStream to ByteArrayInputStream */
        byte[] data = out_barray.toByteArray();
        ByteArrayInputStream in_barray = new ByteArrayInputStream(data);

        Document document = builder.parse(in_barray);

        /* Remove all paragraphs that have *only* text "@removeLine". */
        removeXMLNodes( document, "//*[name() = 'text:p' and . = '@removeLine']");

        OutputStream out_after_removelines = xdocarchive.getEntryOutputStream("content.xml");
        String str;

        /*
         * XXX:
         * Code below should be written this:
         *
         *     DOMImplementationLS domImplementation = (DOMImplementationLS) document.getImplementation();
         *     LSSerializer lsSerializer = domImplementation.createLSSerializer();
         *     str = lsSerializer.writeToString(document);
         *
         * But, for any reason I don't know, I'm getting this error:
         *
         *     16:07:19,205 SEVERE [application] java.lang.NoSuchMethodError: org.apache.xml.serializer.Serializer.asDOM3Serializer()Ljava/lang/Object;
         *     javax.faces.el.EvaluationException: java.lang.NoSuchMethodError: org.apache.xml.serializer.Serializer.asDOM3Serializer()Ljava/lang/Object;
         *
         * Probably ugly current code base brokens what would be a normal Java environemnt.
         * So I had to make it work using TransformerFactory.
         *
         * More information in: https://stackoverflow.com/questions/46183366/java-lang-nosuchmethoderror-asdom3serializerljava-lang-object-in-java-6
         *
        */

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(document);
        StreamResult result =  new StreamResult(new StringWriter());
        transformer.transform(source, result);
        str = result.getWriter().toString();

        /* Make sure we write the ODT in "UTF-8". */
        out_after_removelines.write(str.getBytes("UTF-8"));

        /* Save "content.xml" within the XDocArchive. */
        out_after_removelines.close();
        XDocArchive.writeEntry(xdocarchive, "content.xml", out_after_removelines);
        return xdocarchive;
}

In summary, after generating our report we changed the content.xml file that is within the ODT zip (by the way, an ODT file is just a zip with several files within), removing all paragraphs that have text @removeLine only, with the help of XML tools Java has.

Troubleshooting

error: type Pair does not take parameters

In my case I was compiling a project in 1.7 where I should use 1.6. Maybe the Pair type changed between these versions?

package com.sun.istack.internal does not exist

An error like that means that you are using a type that exists, but your are not allowed to access it, mostly because it is internal to the Java language. javac forbids it (see this link to understand how) and the only way to override that is to use the -Dignore.symbol.table.

If you are using ant you may use the <compilerarg> tag to pass parameters to the compiler (see this for more information).

Important

Other compilers, like the Eclipse Java Compiler don't have this restriction.

SEVERE [application] java.lang.NoSuchFieldError: ALIAS_TO_ENTITY_MAP

According to this page this error happens when you compile your class using a version of a package and run using another.

The problem I had was that I was compiling it against one version of Hibernate and running it in JBoss with the version that comes with JBoss. The solution I found was to delete *hibernate* files from the lib directory of JBoss.

(Is there a non-destructive solution?)

The type <typename> cannot be resolved. It is indirectly referenced from required .class files

If you look for this error on the web you'll get lots of information regarding the binary compability of JRE/JDK you are using. Make sure your target binary has version that match your Java version. Normally this is a parameter passed to the java compiler and is configured either in IDE settings or in an Ant file (build.xml) or build system file.

My problem, though, was different and simple. When compiling a huge problem, I forgot to add a jar file to the CLASSPATH. Since I was using an Ant build file (build.xml), it was enough to add the following line to the place that defined classpath:

<pathelement location="location/of/the/jar/file" />