Saturday, 11 February 2012

PlantUML and NetBeans

If you're looking to integrate PlantUML with Netbeans with Maven, check out my blogpost here.

Introduction


One of the problems with software designers is that they do not enjoy writing Documentation. I do, but then again, I'm weird.

Now Documentation in the area of Java can be split up into two categories:
  • Javadoc comments, that reside in the Java source code, right where it matters
  • Specs, design documents, etc. which are made when the system is first designed and are then stored on a network drive or (if you're lucky) a version control system. They are never updated, become outdated, and forgotten but are sometimes used to provide new junior designers with a wrong idea of the architecture.
So, ideally, you'd wish to have all the specs on hand in the same fashion as your javadoc, with the hope that any change in the code by dilligent designers is also propagated in the javadoc.6

This is where I find PlantUML1 to be extremely handy.

Installing PlantUML in Netbeans


The following task addition lifted straight from the pages of PlantUML and added to build.xml in the netbeans project.
<!-- task definition -->
<taskdef name="plantuml"
  classname="net.sourceforge.plantuml.ant.PlantUmlTask"
  classpath="plantuml.jar" />


<!-- process ./src files -->
<target depends="javadoc-build" name="build-uml">
    <mkdir dir="${dist.javadoc.dir}/images"/>
    <!-- there is an issue where relative paths do not work -->
    <plantuml output="/home/mrbear/NetBeansProjects/YourProject/${dist.javadoc.dir}/images/" verbose="true">
        <fileset dir="./src">
            <include name="**/*.java" />
            <exclude name="**/*Test.java" />
        </fileset>
    </plantuml>
</target>
This won't work, as plantuml.jar cannot be found automatically. Once you've downloaded it you can let your project know where it is. A good explanation of this can be found at [3].

Running PlantUML and Javadoc


First of all, we add the uml syntax2 to the javadoc comments.
/**
 *
 * <p>Indicates the different sizes that are possible in the displaying
 * of pictures. BIG being un-scaled.</p>
 * <img src="../../images/ImageSize.png"/>
 * @author maartenl
 *
 * @startuml
 * "java.lang.Enum<ImageSize>" <|-- enum ImageSize
 * ImageSize : +ImageSize BIG
 * ImageSize : +ImageSize LARGE
 * ImageSize : +ImageSize MEDIUM
 * ImageSize : +ImageSize THUMB
 * ImageSize : +getHeight()
 * ImageSize : +getWidth()
 * @enduml
 */

public enum ImageSize
{

Build the "build-uml" target. It will automatically generate all the javadocs and start off generating uml diagrams. You can do this in the Files explorer in netbeans, right-click on build.xml on toplevel and select the appropriate run target. When the "build-uml" ant target is started in netbeans, the following output is shown:

main:
Starting PlantUML
Nb images generated: 1
BUILD SUCCESSFUL (total time: 0 seconds)

The webpage will look like the image below![5] VoilĂ , uml diagrams!

Issues

- Two files have the same name, so they both create the same named image file. And they get copied in the ant task, so only one of them remains!

The easiest solution is to add a filename after the "@startuml" command, to indicate the name of the image. This is especially important if you have two or more diagrams within the same Java file. I found it especially convenient when dealing with UML diagrams in package-info.java files.

A better solution would be to update the Ant task to take care of this automatically.

- I'm getting "taskdef class net.sourceforge.plantuml.ant.PlantUmlTask cannot be found using the classloader AntClassLoader[]"!

Make sure the plantuml.jar file is reachable in the classpath.

- Auto formatting in Netbeans of my Java source code throws my carefully created UML specs in the Javadoc into disarray!

Yes, while Eclipse has a /* @formatter:on */ editor annotation, I have been unable to find the same in Netbeans.

For now, the only solution I have found it to turn on 'explicit newlines' in formatting of the javadoc comments. You can do this by going in Netbeans to Tools->Options->Editor->Formatting->select Java->Category Comments and turn on "Preserve New Lines".

- The image shows errors, something like the image below.
Dot Executable: /usr/bin/dot
File does not exist
Cannot find Graphviz. You should try

@startuml
testdot
@enduml

or

java -jar plantuml.jar -testdot

It means you haven't installed the graphviz4 package that takes care of a lot of rendering.

root@localhost:~# apt-get install graphviz
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  libcgraph5 libgvpr1
Suggested packages:
  graphviz-doc
The following NEW packages will be installed:
  graphviz libcgraph5 libgvpr1
0 upgraded, 3 newly installed, 0 to remove and 197 not upgraded.
Need to get 553 kB of archives.
After this operation, 1,741 kB of additional disk space will be used.
Do you want to continue [Y/n]? y
Get:1 http://nl.archive.ubuntu.com/ubuntu/ natty/main libcgraph5 i386 2.26.3-5ubuntu1 [47.8 kB]
Get:2 http://nl.archive.ubuntu.com/ubuntu/ natty/main libgvpr1 i386 2.26.3-5ubuntu1 [198 kB]
Get:3 http://nl.archive.ubuntu.com/ubuntu/ natty/main graphviz i386 2.26.3-5ubuntu1 [307 kB]
Fetched 553 kB in 0s (563 kB/s) 
Selecting previously deselected package libcgraph5.
(Reading database ... 163396 files and directories currently installed.)
Unpacking libcgraph5 (from .../libcgraph5_2.26.3-5ubuntu1_i386.deb) ...
Selecting previously deselected package libgvpr1.
Unpacking libgvpr1 (from .../libgvpr1_2.26.3-5ubuntu1_i386.deb) ...
Selecting previously deselected package graphviz.
Unpacking graphviz (from .../graphviz_2.26.3-5ubuntu1_i386.deb) ...
Processing triggers for man-db ...
Setting up libcgraph5 (2.26.3-5ubuntu1) ...
Setting up libgvpr1 (2.26.3-5ubuntu1) ...
Setting up graphviz (2.26.3-5ubuntu1) ...
Processing triggers for libc-bin ...
ldconfig deferred processing now taking place
Thank the Heavens that I'm still running an old Ubuntu, that downloads the proper (read: old) version of GraphViz. PlantUML, I hear, has issues with the new and improved GraphViz 2.28. 1 2

Unfortunately, I was unable to use a relative path in the output attribute in the build.xml. I hope I can fix this later.

Update: changed ImageSizeEnum to ImageSize. Naming should not contain data type names, according to uncle Bob.

Second Update
: PlantUML according to this now works with the newest Graphviz version.

Third Update: Updated NetBeans javadoc formatting problem with a better solution.

References

[1] PlantUML
http://plantuml.sourceforge.net/
[2] Drawing UML with PlantUML - Language Reference Guide (Version 5737)
http://sourceforge.net/projects/plantuml/files/PlantUML%20Language%20Reference%20Guide.pdf/download
[3] NetbeansFAQ
http://wiki.netbeans.org/FaqAntJunitJar
[4] Graphviz
http://www.graphviz.org/
[5] Example
http://maartenl.github.com/YourPersonalPhotographOrganiser/javadoc/gallery/enums/ImageSize.html
[6] "To keep documentation maintained, it is crucial that it be incorporated in the source program, rather than kept as a separate document ... even high-level language syntax does not at all convey purpose." [DRY principle]
The Mythical Man-Month (Frederick P. Brooks, Jr.)

Tuesday, 24 January 2012

Hibernate issue

Recently encountered a small bug at work, related to Hibernate 3.3.2/3.4.0. It's already solved in the latest versions, but I found out after I spend some time debugging and since nobody at work generally has time/effort to upgrade libraries to the new version...

In other words, perhaps this will help someone out there.

The following java code contained the offending hql statement. It's an insert into select with a subselect in it. Not entirely trivial, in other words. Especially as JPA does not support this sort of statement.

public static final String BULKINSERT = 
                "INSERT INTO Settings " + 
                "(period, section, method) " + 
                "SELECT tp, sc, 'DUM' " + 
                "FROM SectionCode sc, TimePeriod tp " +
                "WHERE sc.sectionId = :sectionId " +
                "AND sc.deleted = false " +
                "AND tp.periodnr = :period " +
                "AND NOT EXISTS (" +
                "    SELECT '' " +
                "    FROM Settings ai " +
                "    WHERE ai.period.periodnr = :period " +
                "    AND ai.section = sc)";

Turning on Hibernate logging in jboss:

<category name="org.hibernate"> 
    <priority value="TRACE"/>  
</category>

I noticed it was translated by Hibernate into the following query.

14:02:21,934 TRACE [QueryPlanCache] located HQL query plan in cache (INSERT INTO Settings (period, section, method) SELECT tp, sc, 'DUM' FROM SectionCode sc, TimePeriod tp WHERE sc.sectionId = :sectionId AND sc.deleted = false AND tp.periodnr = :period AND NOT EXISTS (    SELECT ''     FROM Settings ai     WHERE ai.period.periodnr = :period     AND ai.section = sc))
14:02:21,934 TRACE [HQLQueryPlan] executeUpdate: INSERT INTO Settings (period, section, method) SELECT tp, sc, 'DUM' FROM SectionCode sc, TimePeriod tp WHERE sc.sectionId = :sectionId AND sc.deleted = false AND tp.periodnr = :period AND NOT EXISTS (    SELECT ''     FROM Settings ai     WHERE ai.period.periodnr = :period     AND ai.section = sc)
14:02:21,948 TRACE [QueryParameters] named parameters: {sectionId=W, period=6}
14:02:21,953 DEBUG [AbstractBatcher] about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
14:02:21,953 DEBUG [ConnectionManager] opening JDBC connection
14:02:22,232 DEBUG [SQL] insert into SETTINGS ( SETTINGID, TIMEPERIODID, SECTIONCODEID, METHOD) select SETTING_SEQUENCE.nextval, timeperiod1_.TIMEPERIODID AS col_0_0_, sectioncode0_.SECTIONCODEID AS col_1_0_, 'DUM' AS col_2_0_ FROM SECTIONCODE sectioncode0_, TIMEPERIOD timeperiod1_ WHERE sectioncode0_.SECTIONCODEID = ? AND sectioncode0_.DELETED = 'N' AND timeperiod1_.TIMEPERIODID = ? AND NOT (EXISTS (SELECT '' FROM SETTINGS setting2_ WHERE setting2_.TIMEPERIODID = ? AND setting2_.SECTIONCODEID = SECTIONCODEID

insert into SETTINGS ( SETTINGID, TIMEPERIODID, SECTIONCODEID, METHOD) 
select SETTING_SEQUENCE.nextval, 
timeperiod1_.TIMEPERIODID AS col_0_0_,
       sectioncode0_.SECTIONCODEID AS col_1_0_,
       'DUM' AS col_2_0_
  FROM SECTIONCODE sectioncode0_, TIMEPERIOD timeperiod1_
 WHERE     sectioncode0_.SECTIONCODEID = 'W'
       AND sectioncode0_.DELETED = 'N'
       AND timeperiod1_.TIMEPERIODID = 6
       AND NOT (EXISTS
                   (SELECT ''
                      FROM SETTINGS setting2_
                     WHERE setting2_.TIMEPERIODID = 6
                           AND setting2_.SECTIONCODEID =
                                  SECTIONCODEID));

It's hard to tell at first, but in the output above, in the subselect, it says "AND setting2_.SECTIONCODEID = SECTIONCODEID". It should be "AND setting2_.SECTIONCODEID = sectioncode0_.SECTIONCODEID". The current implementation causes the second SECTIONCODEID to automatically refer back to settings2_ table, causing the expression to always evaluate to true. This explained why I never saw any records being added in certain cases.

In order to get the query to work properly, I was forced to reach out to native SQL instead of HQL. Upgrading our software to the latest and greatest Hibernate is a tad too involved for now.

References

HHH-5274 - HQL-Insert with Select and Sub-Select fails
https://hibernate.onjira.com/browse/HHH-5274