Extreme Java When vanilla Java is not enough

10Jun/080

Adding special chars to NetBeans action name

This one I posted in NetBeans mailing lists. If you want to show a special char (like a slash - "/") in the action display name (i.e. in the way your user will see it), the right way is to pass it to the AbstractAction constructor (just like the templates of NetBeans):

public class SpecialCopyPasteAction extends AbstractAction {
 
  public SpecialCopyPasteAction() {
    super(NbBundle.getMessage(SpecialCopyPasteAction.class,
        "CTL_SpecialCopyPasteAction"));
  }
 
  public void actionPerformed(ActionEvent evt) {
    // TODO: Implement it
  }
}

And, in Bundle.properties:

CTL_SpecialCopyPasteAction=Special Copy/Paste

Remember that the action name in layer.xml is mapped to a system file name. You cannot put a "/" on file names, even on Windows. This is one of the reasons NB filesystem has the localization feature.

12May/080

Custom project file structure in NetBeans – new menu item in projects

In a previous post, I’ve told that I want to use NetBeans and share projects with developers that still use Eclipse. In another post, I added a custom folder to the project view.

Now, I will add a custom menu item to run an ant task outside NB's build scripts:

  1. Assuming you have a NB module project ready, you just need to add a new entry in your layer.xml:
    <folder name="Projects">
      <folder name="Actions">
        <file name="org-mycompany-MyMenuAction.instance"/>
      </folder>
    </folder>
  2. Create a new class with the same name (org.mycompany.MyMenuAction):
    public final class TransitMenuAction
      extends AbstractAction
      implements Presenter.Popup, ContextAwareAction {
     
      public final static String DISPLAY_NAME =
        NbBundle.getMessage(TransitMenuAction.class,
          "CTL_TransitMenuAction");
     
      private Lookup context;
     
      public TransitMenuAction() {
        this(null);
      }
      public TransitMenuAction(Lookup context) {
        super(DISPLAY_NAME);
        this.context = context;
      }
     
      public void actionPerformed(ActionEvent e) {
        // I'm a menu container... I do nothing
      }
     
      public Action createContextAwareInstance(
        Lookup context) {
        return new MyMenuAction(context);
      }
      public JMenuItem getPopupPresenter() {
        return new PopupPresenter();
      }
     
      /** Dynamic menu factory */
      class PopupPresenter extends JMenuItem
        implements DynamicMenuContent {
     
        public JComponent[] getMenuPresenters() {
          Project proj = context.lookup(Project.class);
          // MyUtils comes from last post
          if (!MyUtils.isMyProject(proj)) {
            // hides this menu
            return new JComponent[0];
          }
     
          JMenu mnu = new JMenu(DISPLAY_NAME);
          mnu.add(new PackageAction(context));
          return new JComponent[] { mnu };
        }
        public JComponent[] synchMenuPresenters(
          JComponent[] items) {
          return items;
        }
      }
    }
  3. And, the PackageAction is:
    public class PackageAction extends AbstractAction {
      private Lookup context;
      public PackageAction(Lookup context) {
        super(NbBundle.getMessage(PackageAction.class,
          "CTL_PackageAction"));
        this.context = context;
      }
     
      /* This action will copy a file and run an Ant task */
      public void actionPerformed(ActionEvent e) {
        try {
          /* Both the file and the build scripts are under
           * the "ant" folder in project directory
           */
          Project proj = context.lookup(Project.class);
          FileObject antFolder = proj.getProjectDirectory().
            getFileObject("ant");
     
          // Read the original file
          FileObject def = antFolder.
            getFileObject("compile-default.properties");
          Properties prop = new Properties();
          prop.load(def.getInputStream());
     
          // Change a little bit
          // TODO: Add this to a property window
          prop.setProperty("compile.lib.ext",
            "C:\\etc\\apache-tomcat-6.0.14\\lib");
     
          // Write to the copy file
          FileObject cpy = antFolder
            .getFileObject("compile.properties");
          if (cpy == null) {
            cpy = antFolder.createData("compile.properties");
          }
          FileLock lock = cpy.lock();
          try {
            prop.store(cpy.getOutputStream(lock),
              "any cool comment");
          } finally {
            lock.releaseLock();
          }
     
          // Now, we will run Ant - grab the script...
          FileObject buildXML = antFolder
            .getFileObject("build.xml");
     
          // ... and run the "package" target
          ActionUtils.runTarget(buildXML,
             new String[]{ "package" }, null);
        } catch (IOException ex) {
          Exceptions.printStackTrace(ex);
        } catch (IllegalArgumentException ex) {
          Exceptions.printStackTrace(ex);
        }
      }
    }
  4. Last, but not least, the project dependencies:
    1. Ant (ActionUtils)
    2. Filesystem API (FileObject and FileLock);
    3. Project API (Project);
    4. UI Utilities API (DynamicMenuContent); and
    5. Utilities API (Lookup)

And, your new project menu item (that calls an Ant task) is ready.

12May/080

Custom project file structure in NetBeans – adding folder

In a previous post, I've told that I want to use NetBeans and share projects with developers that still use Eclipse. Since we have a custom requirements, I plan to customize the way NB handle these projects.

As you will notice, every NB project customization will go into the layer file, under "Projects" folder. You will be surprised on how easy it goes.

In this post, my task is to make a "config" folder visible under "Projects" tab of NetBeans. Assuming you already have a NetBeans module project ready, steps are:

  1. Create an utility class with this method:
    public static boolean isMyProject(Project proj) {
      // check if it is a nb project
      if (proj.getProjectDirectory().getFileObject("nbproject") ==
        null) {
        return false;
      }
      // check if our custom folder exists
      return (proj.getProjectDirectory().getFileObject("config") !=
        null);
    }
  2. In your layer.xml, create an entry named "Projects / type-of-your-desired-project / Nodes". You can look into "Important Files / XML Layer / Projects / org-netbeans-modules-xxx-project / Nodes" and right click on it to add a new file - NB will add the "folder" tags automatically;
  3. Inside "Nodes", add a file named "org-mycompany-MyCompanyNodeFactory.instance" (or rename the file if you created it on step 1);
  4. Now, create a class named "org.mycompany.MyCompanyNodeFactory" that implements NodeFactory. This is a pretty small one:
    public class MyNodeFactoryImpl implements NodeFactory {
      public NodeList createNodes(Project proj) {
        if (MyUtils.isMyProject(project) {
          try {
            MyFilesNode nd = new MyFilesNode(proj);
            return NodeFactorySupport.fixedNodeList(nd);
          } catch (DataObjectNotFoundException ex) {
            Exceptions.printStackTrace(ex);
          }
        }
        // if we can't use it, return an empty list
        return NodeFactorySupport.fixedNodeList();
      }
    }
  5. Create the MyFilesNode. It must be a child of Node. You can do anything you want here (just Google it and you will see what I mean). Mine is:
    public class MyFilesNode extends AbstractNode {
      private static Image smallImage =
              Utilities.loadImage("/path-to/icon-8x8.gif"); // NOI18N
     
      public MyFilesNode(Project proj) {
        super(Children.create(new MyChildFactory(proj), true);
      }
      public String getDisplayName() {
        return "My Files";
      }
      // A bonus: this snippet will merge NB folder icon and
      // your 8x8 icon
      @Override
      public Image getIcon(int type) {
        DataFolder root = DataFolder.findFolder(
          Repository.getDefault().getDefaultFileSystem().getRoot());
        Image original = root.getNodeDelegate().getIcon(type);
        return Utilities.mergeImages(original, smallImage, 7, 7);
      }
      @Override
      public Image getOpenedIcon(int type) {
        DataFolder root = DataFolder.findFolder(
          Repository.getDefault().getDefaultFileSystem().getRoot());
        Image original = root.getNodeDelegate().getIcon(type);
        return Utilities.mergeImages(original, smallImage, 7, 7);
      }
    }

    And, of course, the ChildFactory is:

    public class MyChildFactory extends ChildFactory<String> {
      private Project proj;
     
      public TransitFilesChildren(Project proj) {
        this.proj = proj;
      }
     
      /* Since a node is the visual representation of
       * something else, this method builds the list
       * of "something else"s we want to show. If this
       * was a business application, the "key" could be
       * a value object. In this module, the key will
       * be the name of the custom folder.
       */
      @Override
      protected boolean createKeys(List<String> keys) {
        keys.add("ant");    // another folder - why not?
        keys.add("config");
        return true;
      }
     
      @Override
      protected Node createNodeForKey(String key) {
        // 1. Find the FileObject for the folder
        FileObject fo = proj.getProjectDirectory().getFileObject(key);
        // 2. Find the DataFolder for the FileObject
        DataFolder df = DataFolder.findFolder(fo);
        // 3. Create a custom filter
        DataFilter filter = new MyDataFilter();
        // 4. Get DataFolder children (with filter)
        Children children = df.createNodeChildren(filter);
        // 5. Create a proxy lookup
        Lookup lookup = new ProxyLookup(new Lookup[]{
          df.getNodeDelegate().getLookup(), proj.getLookup()
        });
        // 6. Create a filter node
        return new FilterNode(df.getNodeDelegate(), children, lookup);
      }
     
      /**
       * Filter DataObjects that must not be shown (like .svn folders)
       */
      static final class MyDataFilter implements ChangeListener,
        ChangeableDataFilter {
     
        private final ChangeSupport changeSupport =
          new ChangeSupport(this);
     
        public MyDataFilter() {
          VisibilityQuery.getDefault().addChangeListener(this);
        }
        public boolean acceptDataObject(DataObject obj) {
          FileObject fo = obj.getPrimaryFile();
          return VisibilityQuery.getDefault().isVisible(fo);
        }
        public void stateChanged(ChangeEvent e) {
          changeSupport.fireChange();
        }
        public void addChangeListener(ChangeListener listener) {
          changeSupport.addChangeListener(listener);
        }
        public void removeChangeListener(ChangeListener listener) {
          changeSupport.removeChangeListener(listener);
        }
      }
    }
  6. Now, go toproject properties and add the following modules (you can also search for the needed classes):
    1. Datasystems API (ChangeableDataFilter, DataFilter, DataFolder and DataObject);
    2. Filesystem API (FileObject);
    3. General Queries API (VisibilityQuery);
    4. Nodes API (ChildFactory, Children, FilterNode and Node);
    5. Project API (Project);
    6. Utilities API (ChangeSupport, Lookup and ProxyLookup);

Now, if you run the project, you will notice the new folder under Projects view.

12May/080

Custom project file structure in NetBeans

In my job, they officially use Eclipse Europa to develop. I really dislike Eclipse - it is a matter of taste. Because of this, I'm using NetBeans ability to share projects (like in this demo) - and it works like a charm.

But, since even rose has its thorn, they have some custom tasks here:

  1. Inside every project, there's a "config" folder with some files that must be copied to a "/var/xxx" folder before running the application;
  2. A custom ant build script to create a ZIP file that will go to test and production environments; and
  3. The file from step 2 must be transfered via SFTP to specific servers, on a folder named like "/var/deploy/xxx".

These are manually-repeated tasks that must be done for every project. Eclipse users (here) must do it step-by-step. NetBeans users will use a (custom) brand-new module that I developed to do these tasks.

Just to enumerate, we need to solve these problems:

  1. The "config" folder must be visible in "Projects" tab (it is under "Files" tab, but it is ugly);
  2. An ant task in a custom build script must be called whenever I want;
  3. The "config" folder must be copied to "/var/xxx" when the project is compiled;
  4. An ZIP file must be sent via SFTP to a "/var/deploy/xxx" folder;
  5. I don't want to hardcode the "xxx" - what about a new tab on project properties?

OK. We have a lot of things to cover here. But, they are very simple. Since it could become a large post, I'll cover them in other posts (that will be have a pingback here, on comments section).

3Apr/080

One more reason to love NetBeans

I'm trying NetBeans 6.1 Beta. It is pretty stable to a beta, but I got a little problem: the "Find Usages" resource has some kind of memory leak. I sent the log using NetBeans' built-in feature and voilá: it was sent successfully, and, like a charm, the server told me the issue was already sent by another user and is FIXED - yup, they are quick.

For the folks that laugh at Windows "send error" button, just take a look at the screenshot:

Upload of NetBeans Error Log

And, if you want to, you can see the report number 13304, and the issue #125531.

8Jan/082

Blackmagic with NetBeans and OpenOffice

This one is one of the weirdest (and useful) things I ever seen in 16 years of my life as a developer (I'm 26, BTW)... OpenOffice is a very powerful Office Automation solution... NetBeans Plaform is a very powerful application platform... Can you imagine what you get when you mix both together? Yup, this is possible. With NB and OOo installed, you just need to create a NB Module with following JARs from OOo:

  • juh.jar
  • jurt.jar
  • officebean.jar
  • ridl.jar
  • unoil.jar

I created another module with a single TopComponent, and add this snippet:

private OOoBean oo;
 
@Override
public void componentOpened() {
  add(oo = new OOoBean());
  oo.loadFromURL("private:factory/swriter", null);
  oo.aquireSystemWindow();
}

But, this will NOT work... If you try to run it, an exception will be thrown, because OOo libs (.DLL/.SO) will not be found. They only exists in OOo install folder, and are automagically found by OOo classes - only if they aren't moved from the "classes" folder in OOo installation.

The workaround is a couple of classes in OOo SDK and The Ultimate Blackmagic(TM) - patent pending:

  1. Grab odk / source / com / sun / star / lib / loader from OOo's CVS and add it to integration module.
  2. Add unowinreg.dll to Windows Path (if you are on Linux, skip this step).
  3. We need to add "officebean" to classpath. Here lies OOoBean, and is optional if you are only using UNO interfaces. Other JARs are correctly listed by UnoInfo. Alter Loader.java and add this after line 205 (after "UnoInfo" call):
    vec.add(new File(fClassesDir, "officebean.jar").toURL());
  4. Now, the ugliest part of the evil potion: create a Facade. Using OOoBean directly will lead to some ugly ClassLoader conflicts. I've no time to fix this, but the following works:
    public static Container buildOOoBean() {
      return (Container) Loader.getCustomLoader().
        loadClass("com.sun.star.comp.beans.OOoBean").
        newInstance();
     
    }
    public static void showOOoBean(Container oo) {
      Class c = oo.getClass().
        getClassLoader().
        loadClass("com.sun.star.beans.PropertyValue");
     
      Class ca = Array.newInstance(c, 0).getClass();
      Method m = oo.getClass().
       getMethod("loadFromURL", String.class, ca);
     
      m.invoke(oo, "private:factory/swriter", null);
      oo.getClass().
        getMethod("aquireSystemWindow").invoke(oo);
    }
  5. Back to the TC, I use "buildOOoBean" in ComponentOpened and "showOOoBean" AFTER the component is shown (does not work otherwise).

This level of indirection is mandatory, because client module's ClassLoader is different than integration module's. If I do "OOoBean oo = new OOoBean()", the declaration and instantiation will try to use the JARs bundled. Since they don't lie on OOo install folder, the libraries will not be found. If I use "oo = (OOoBean) Loader.getCustomLoader().getClass("...").newInstance()", the "OOoBean" class from declaration will NOT be the same as the class instantiated. If you ever worked with JavaEE 4 and JSF/Struts/etc, you know that same classes from different ClassLoaders throws ClassCastException.

Yes, it is the purest evilness, but works (with some bugs):

Screenshot of OpenOffice and NetBeans

OOo works really well with Java, but NetBeans ClassLoader is "self-contained" (i.e. your bundled application must include EVERYTHING you use). This is a very nice architecture, but does not help if you if you need to interface with external applications.

Tagged as: 2 Comments
4Jan/080

Asynchronous Node children in NetBeans Platform

5 minutes playing with the new NetBeans 6 platform and I found a very useful feature. In previous post, I download URL. Using that code, I want to create a list of POJOs (that will be represented by a list of Nodes). In old NB5 days, I had to create a Thread and a lot of syncronization code to make the download run in background. In NetBeans 6 is easy as 1, 2, 3:

  1. Subclass ChildFactory (from Nodes API), implementing createKeys and createNodeFromKey
  2. Create an AbstractNode passing Children.create(MyChildFactory)
  3. Compile, run and enjoy!
18Mar/070

The quest for the Java Portal – Profiling Liferay

It's time to run the Profiler. I start JBoss with "run-nbprofiler.bat" (created by NetBeans) and ask NB to attach profiler. I'm using "analyze performance", filtering Java core classes. JBoss runs very slow, but this is normal, since profiler is collecting execution of every method.

My first try showed that Lucene is called a lot of times, even when server is on "idle" state. Maybe it is a background job that should have a smaller frequency.

The second try filters out Lucene classes (using NB Profiler options). I found two bootlenecks: com.liferay.portal.lucene.IndexWriterFactory.decrement (takes 58% of startup time) and JBoss classloaders. And I found that Liferay auto deploy is running too often (~5 seconds of delay). Nice for develoment, awful for production use.

I had to remove JBoss classes from profiling, and I got amazed on things I discovered. I got a OutOfMemoryError (perm gen space) - solved by adding "-XX:MaxPermSize=256m" to run script - but I could find a lot of interesting things:

  1. getResourceAsStream was called 12068 times;
  2. Xerces' ChildNode was instantiated 853808 times (does anybody imagine why Java is "slow"?);
  3. Stacktrace got really deep. About 50 levels or more, not including JBoss, Lucene and JDK classes;
  4. A chain of 7 filters was called before hitting Liferay's MainServlet. If you consider a little forward made after MainServlet, we reach 14 filters before hitting a JSP file;
  5. JSP compilation took 19s on this environment - total execution was 180s;
  6. LR's VelocityTaglib has 8 iconXXX() methods that took 8 seconds each - detail: each one forwards another request;
  7. Everytime a "include" is made, the chain of 7 filter is called. And there's a LOT of includes.

Remember that this was only ONE hit and the request wasn't complete, because of the OOM error. After that, I'm going to have some fun in the "real world". I'll try to go deeper tomorrow. Maybe I can send a RFE to Liferay team after I organize the arguments.

18Mar/070

The quest for the Java Portal – Running Liferay

Now, I have a profiled environment. But, when I tried to run JBoss (without profiling), I got an error in counter-ejb module. Its classpath is not correctand, so, I added this line to my build.[user|computer].properties:

classpath.manifest=[original line in build.properties]
                   lib/doubleclick-filter.jar

I reported this bug on Liferay JIRA [#LEP-2406]. After another 16 minutes of compilation, I forgot to initialize the database. I had two options: use a diferent connection pool or initialize it before deploying Liferay. I prefer the second, so, the easiest way was to create a MBean that depends on LiferayPool. Hypersonic is smart enough to allow multiple SQL commands in one Statement. I will upload the code later, but it is a matter of create a MBean that reads the script provided by Liferay and runs it on the poll.

This leads to an strange error about Spring transactions with EJB, Hibernate, JBoss and all. I don't remember the details, but the solution is to change the debug attribute of CachedConnectionManager. This is something I can't do in my shared JBoss server, so, I used an dirty trick:

  1. Start JBoss with no Liferay, but with the original data source (liferay-db.xml configured);
  2. JBoss translates your -ds file into and -service file and logs it with debug level;
  3. Grab the translated file in log, create the -service, delete the -ds and indent the file - this will help you understand its structure;
  4. Now, the funny part: copy CacheConnectionManager definition from jboss-jca.xml, paste into liferay-service.xml (inside the "CachedConnectionManager" optional attribute), and rename the MBean to an unique name - I put a ",name=xxx" suffix.

This will create a custom CCM to Liferay, without violating the original instance. That's what I love in Java (specially in JBoss): you can create a Lego-like software that is just a matter of do the bindings.

Liferay runs fine, but I found two bugs: a ClassNotFoundException about ical4j, and the contents of Guest community are blank. The first, I solve by adding lib/ical4j.jar to manifest classpath (as above). The second I don't care, since I will clean everything when I deploy the real application.

16Mar/070

The quest for the Java Portal – Compiling Liferay

I got some weird exceptions with JBoss Portal, so, I decided to play around with Liferay. I have it running on my shared JBoss, and I have some ideas to their structured articles feature, so I will try harder on Liferay.

I've decided to check the bootlenecks on Liferay, because it is too slow. I'll use NetBeans Profiler. Using NB's ability to create projects using existing Ant scripts, I've done some setup:

  1. Create an Java Application project using an already existing Ant script;
  2. Add all "src" folder to the source folders list - about 20 of them (I guess this isn't necessary unless we want to change anything);
  3. Adjust compilation build to the "start" target and run to "deploy" (Liferay does not set "start" as dependency to "deploy");
  4. Create a "build.[user|computer].properties" and "app.server.[user|computer].properties" to customize some build parameters. I dislike Jikes, so, I'm using "javac.compiler=modern". The rest of properties are straighforward to customize, but folders on app.server must be correctly configured - JBoss predefined values does not work on an out-of-box installation;
  5. After building a lot of modules (about 10 minutes on an almost empty Windows box powered by an Athlon XP 3200+), more than 2000 classes - yes, two thousands - are compiled without errors. Running the "deploy" target will install Liferay on JBoss. It installs some JARs on server's lib folder - I will change this later, before uploading to the real server.

Now, you can run JBoss. But first, some one-time configuration:

  1. Add a datasource. To use profiling, I created a memory-only HSQLDB:
    <?xml version="1.0" encoding="UTF-8"?>
    <datasources>
     <local-tx-datasource>
      <jndi-name>jdbc/LiferayPool</jndi-name>
      <connection-url>jdbc:hsqldb:.</connection-url>
      <driver-class>org.hsqldb.jdbcDriver</driver-class>
      <user-name>sa</user-name>
      <password></password>
     </local-tx-datasource>
    </datasources>
  2. Use NetBeans Profiler (Profile | Attach Profiler) to prepare a special run script to active profiling on JBoss - this file will be called "run-nbprofiler".

Now, we have an working environment. Next step is test and profile.