X

Geertjan's Blog

  • November 5, 2010

How to Create Database-Based Project Types

Geertjan Wielenga
Product Manager
If you follow the NetBeans Project Type Tutorial, you'll end up with a new NetBeans Platform project type that will treat as a project any folder that has a subfolder named "texts". Let's now assume that we'd like each project in our application to represent a database, instead of a folder of documents. Very simple to do this, just follow the tutorial exactly as described (go here for the Maven version of that tutorial) and then replace "texts" with ".derby" or whatever the main folder of your particular database is. Now, any folder that has a subfolder ".derby" will be recognized as a new project and you'll be able to open that into your NetBeans Platform application.

Step 1: Set Up Folders On Disk

To try out the scenario described above, create a folder structure like this, using the Derby "sample" database that comes with NetBeans IDE:

I.e., you'll have folder structures on disk with ".derby" as a subfolder within another folder. The "another folder" will be the root folder (with any name you like), identified by the ".derby" subfolder. Within the latter, i.e., within the ".derby" folder, you'll have the database that should be opened in the application.

Step 2: Create the Domain Module

In your application, you need to have domain classes that represent the underlying database. In my case, I've used the "sample" database that comes with NetBeans IDE, then I used the "Entity Classes from Database" wizard to generate the related entity classes directly into a Maven-based NetBeans module, named "CustomerSalesDomain". That wizard also creates the related "persistence.xml" file.

Let's say you want to use embedded Derby, together with a Derby database that is found on disk, so change the lines that are shown in bold, so that they match the lines shown below:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="com.customer_CustomerSalesDomain_nbm_1.0-SNAPSHOTPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>com.customers.sales.Customer</class>
<class>com.customers.sales.DiscountCode</class>
<properties><property name="javax.persistence.jdbc.url" value="jdbc:derby:.derby/sample"/>
<property name="javax.persistence.jdbc.password" value="app"/><property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="javax.persistence.jdbc.user" value="app"/>
</properties>
</persistence-unit>
</persistence>

So, the whole set of domain classes, together with a tuned "persistence.xml" file, are now found within one specific NetBeans module.

Make sure to make the package containing the domain classes public, as well as the package containing "javax.persistence". Right-click the domain module, choose Properties, and use the "Public Packages" tab to set these two packages as public.

The final step in the domain module is to set Derby as a runtime dependency:

<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.6.1.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.6.1.0</version>
<scope>runtime</scope>
</dependency>

Step 3: Create the Functionality Module

Next, create a second module, named "CustomerSalesProjectType", which will provide the support for the database-based project type. That's where you create the new project type (as described in the first paragraph above), for folders that have ".derby" as a subfolder.

When you run the application, you should be able to open the projects shown in the screenshot at the start of this blog entry:

Next, we'll create an Action for opening the database relevant to the currently selected project. Use the New Action wizard to create a conditionally enabled Action class that is sensitive to the Project class. Specify that the Action class should be generated into the "CustomerSalesActions" category. Optionally, specify that the Action should be invoked from a menu item or toolbar button, since we're going to hook it into the list of context-sensitive Actions that will appear in the popup menu of our project type. Name the Action "OpenDatabaseAction", with "Open Database" as the display name.

When you click Finish, stuff is generated into the layer file so that the NetBeans Platform will handle all the plumbing for you. Add this attribute to the list of attributes for your Action in the layer, so that the Action will be performed asynchronously:

<attr name="asynchronous" boolvalue="true"/>

More on the above can be read here. (Later, if you don't see the progress bar while the data is being loaded from the database, it will be because you haven't set the above attribute in the layer.)

In the generated Java class, you'll see the following:

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import org.netbeans.api.project.Project;
public final class OpenDatabaseAction implements ActionListener {
private final Project context;
public OpenDatabaseAction(Project context) {
this.context = context;
}
public void actionPerformed(ActionEvent ev) {
// TODO use context
}
}

For the moment, let's put something into the "actionPerformed", like this:

public void actionPerformed(ActionEvent ev) {
String projectName = context.getProjectDirectory().getName();
StatusDisplayer.getDefault().setStatusText("Opening database for " + projectName);
}

So, let's now hook the above class into the list of Actions on the popup (i.e., the context-sensitive Actions) of our project type. Open the "CustomerSalesProjectLogicalView" class and change the "getActions" method so that our Action is loaded from the layer:

@Override
public Action[] getActions(boolean arg0) {
Action[] nodeActions = new Action[6];nodeActions[0] = Utilities.actionsForPath("Actions/CustomerSalesActions").get(0);
nodeActions[1] = CommonProjectActions.newFileAction();
nodeActions[2] = CommonProjectActions.copyProjectAction();
nodeActions[3] = CommonProjectActions.deleteProjectAction();
nodeActions[4] = CommonProjectActions.setAsMainProjectAction();
nodeActions[5] = CommonProjectActions.closeProjectAction();
return nodeActions;
}

Great. Now run the application again and each project will have the Action defined above:

Step 4: Connecting to the Project-Specific Database

Right. Now we need to actually load the data from the current database. This is where things become interesting, because we're getting to a point where we're doing something that's actually really useful.

Copy this utility class (found somewhere online) into your module. It will let us set properties for accessing the database on the fly. Really handy. It will enable us to switch databases, depending on the location of the currently selected project, since we know that the ".derby" database is always going to be in a fixed place, since it defines our project structure.

Next, change the "actionPerformed" in the "OpenDatabaseAction" to the following:

public void actionPerformed(ActionEvent ev) {//Get the properties we want to assign on the fly:
DBProps config = new DBProps();//Get the project directory:
FileObject projDir = context.getProjectDirectory();//Define the connextion string programmatically:
config.setDbHost(projDir.getPath() + "/.derby");//User/password/database can also be set programmatically:
config.setDatabase("sample");
config.setUsername("app");
config.setPassword("app");//Start the progress bar:
ProgressHandle handle = ProgressHandleFactory.createHandle("Loading data for " + context.getProjectDirectory().getName() + "...");
handle.start();//Set the entity manager, passing in our properties:
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("com.customer_CustomerSalesDomain_nbm_1.0-SNAPSHOTPU", config.buildPersistenceProperties());
EntityManager entityManager = entityManagerFactory.createEntityManager();//Do a query and get the results:
Query query = entityManager.createQuery("Select c From Customer c");//Create a new tab in the Output window,
//return false so that everything will print
//in the same tab, instead of creating a new one:

IOProvider ioProvider = IOProvider.getDefault();
InputOutput inOut = ioProvider.getIO("Customers", false);//Iterate through the results and print in the Output tab:
List<Customer> resultList = query.getResultList();
for (Customer customer : resultList) {
inOut.getOut().println(customer.getName());
}Stop the progress bar:
handle.finish();
}

Above, you're using the "Customer" class from the domain module. To do so, remember that the package containing that class in the domain module must be public and that you must set a dependency on that module in the project type module. That's also how you'll have access to the "javax.persistence" package, since you've exposed that in your domain module.

Now clean and build the parent project and then run the application module. Open the projects you have on disk and right-click on them. Notice the output in the Output window. Notice that you can switch between databases.

Next time we'll look at how to populate a window with the data retrieved from a database, how to switch between different views for different databases, and... how to diff different databases using the Diff API shown yesterday. And all of it in a Maven based NetBeans Platform application.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.