blob: 6d2b71038b6c5e861214c765599a167ba08d0b2e [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
= NetBeans Visual Database Explorer Tutorial
:jbake-type: platform_tutorial
:jbake-tags: tutorials
:jbake-status: published
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:icons: font
:experimental:
:description: NetBeans Visual Database Explorer Tutorial - Apache NetBeans
:keywords: Apache NetBeans Platform, Platform Tutorials, NetBeans Visual Database Explorer Tutorial
In this tutorial, you will learn how to create a visualizer on top of the content of a database. You will create an action, attach it to a connection node, and then use it to connect to the database, visualizing its content in a Visual Library scene, as shown below:
image::images/db_69-db-1.png[]
All the information you need to know for working with the Visual Library API, as well as with the Database Explorer API, is collected at the following locations:
* link:https://netbeans.apache.org/graph/[Visual Library Project Page]
* link:https://netbeans.apache.org/graph/documentation.html[Visual Library 2.0 - Documentation]
* link:http://bits.netbeans.org/dev/javadoc/org-netbeans-api-visual/overview-summary.html[Visual Library API Javadoc]
* link:http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-db/overview-summary.html[Database Explorer API Javadoc]
Also, see link:http://www.javalobby.org/eps/netbeans_visual_library/[Roman Strobl's Visual Library Screencast] on Javalobby.
Before continuing, you are assumed to have read the link:https://netbeans.apache.org/tutorials/nbm-visual_library.html[NetBeans Visual Library Tutorial], which gives you the basics for working with the Visual Library. The basics discussed in that tutorial will not be repeated here.
== Setting Up the Module
In this section, we use wizards to create a module project and a custom window component.
[start=1]
1. Choose File > New Project (Ctrl+Shift+N). Under Categories, select NetBeans Modules. Under Projects, select Module. Click Next.
[start=2]
1. In the Name and Location panel, type ``DatabaseExplorerDemo`` in the Project Name field. Change the Project Location to any directory on your computer. Leave the Standalone Module option and Set as Main Project checkbox selected. Click Next.
[start=3]
1. In the Basic Module Configuration panel, type ``org.demo`` in Code Name Base.
[start=4]
1.
Select "Generate XML Layer". Leave the locations of both the localizing bundle and the XML layer file so that they will be stored in a package with the name ``org/demo`` . Click Finish.
The IDE creates the ``DatabaseExplorerDemo`` project. The project contains all of your sources and project metadata, such as the project's Ant build script. The project opens in the IDE. You can view its logical structure in the Projects window (Ctrl+1) and its file structure in the Files window (Ctrl+2).
[start=5]
1. Right-click the project, choose Properties, click Libraries in the Project Properties dialog box and declare a dependency on the following APIs:
* link:http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-db/overview-summary.html[Database Explorer]
* link:http://bits.netbeans.org/dev/javadoc/org-openide-nodes/overview-summary.html[Nodes API]
* link:http://bits.netbeans.org/dev/javadoc/org-openide-awt/overview-summary.html[UI Utilities API]
* link:http://bits.netbeans.org/dev/javadoc/org-openide-util/overview-summary.html[Utilities API]
* link:http://bits.netbeans.org/dev/javadoc/org-netbeans-api-visual/overview-summary.html[Visual Library API]
* link:http://bits.netbeans.org/dev/javadoc/org-openide-windows/overview-summary.html[Window System API]
NOTE: You need to set an implementation dependency on the Database Explorer API. Do so by selecting the Database Explorer item in the Libraries panel of the Project Properties dialog, clicking Edit, and then clicking 'Implementation Version'. By doing this, you are setting a dependency on internal classes that will change in future releases. Currently there is no other way of completing the scenario in this tutorial than to set an implementation dependency.
[start=6]
1. Click OK to close the Project Properties dialog.
[start=7]
1. Create a package named "org.demo.resources" and put the following images into it:
image::images/db_cancelGlyph.png[]
image::images/db_command_16.png[]
image::images/db_custom_displayable_16.png[]
image::images/db_item_16.png[]
image::images/db_list_16.png[]
image::images/db_postCodeGlyph.png[]
image::images/db_preCodeGlyph.png[]
== Creating the Action
We will create an action that will only be enabled if the activated node has the ``ConnectionNode`` from the Database Explorer API in its Lookup.
[start=1]
1. Right-click the module project, and create a new class extending the ``BaseAction`` class from the Database Explorer API:
[source,java]
----
import java.sql.Connection;
import org.netbeans.api.db.explorer.ConnectionManager;
import org.netbeans.api.db.explorer.DatabaseConnection;
import org.netbeans.modules.db.explorer.action.BaseAction;
import org.openide.nodes.Node;
import org.openide.util.HelpCtx;
public final class ShowDatabaseStructureAction extends BaseAction {
@Override
protected void performAction(Node[] nodes) {
}
@Override
protected boolean enable(Node[] activatedNodes) {
return true;
}
@Override
public String getName() {
return "Show Database Structure";
}
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
}
----
[start=2]
1. Register the Action class as follows:
[source,xml]
----
<folder name="Databases">
<folder name="Explorer">
<folder name="Connection">
<folder name="Actions">
<file name="org-demo-ShowDatabaseStructureAction.instance"/>
</folder>
</folder>
</folder>
</folder>
----
You have now registered an Action such that it will be attached to the connection node in the Services window:
image::images/db_69-db-3.png[]
== Creating the Window
In this section, we create a new window component that will hold our scene.
[start=1]
1. Right-click the module project, choose New > Other and choose Window from the Module Development category. Click Next.
[start=2]
1. Choose ``editor`` in the drop-down list. Do not select Open on Application Start. Click Next.
[start=3]
1. Type ``Demo`` in Class Name Prefix. Optionally, add an icon with a dimension of 16x16 pixels. Click Finish.
[start=4]
1. Right-click in the ``DemoTopComponent`` in Design mode, choose Set Layout, and select BorderLayout.
[start=5]
1. Switch to Source mode and change the ``getPersistenceType`` method to return ``TopComponent.PERSISTENCE_NEVER`` .
== Creating the Scene
Programming with the Visual Library API is similar to programming in Swing. You build and modify a tree of visual elements that are called "widgets". The root of the tree is represented by a Scene class which holds all the visual data of the scene. The scene is a widget. You have to create a scene view, which is a JComponent. You must then add the JComponent to a JScrollPane.
In this section, we add a JScrollPane to our TopComponent. Then we create a new Visual Library scene in a separate Java source file. Next, we pass the scene to the TopComponent, so that it can be displayed in the TopComponent's JScrollPane. We then install the module project and display our first scene.
[start=1]
1. Use the Palette (Ctrl-Shift-8) to drop a ``JScrollPane`` on the TopComponent.
[start=2]
1.
In the ``org.demo`` package, create a Java class called ``DBGraphScene`` . Let the class extend ``VMDGraphScene`` .
A red error underline and a lightbulb appears. Let the IDE generate the import statement.
A red error underline and a lightbulb appears again. Let the IDE generate the class's abstract methods.
[start=3]
1. Replace the content of the class with the following:
[source,java]
----
import java.awt.Image;
import java.awt.Point;
import java.util.Arrays;
import java.util.List;
import org.netbeans.api.visual.vmd.VMDGraphScene;
import org.netbeans.api.visual.vmd.VMDNodeWidget;
import org.netbeans.api.visual.vmd.VMDPinWidget;
import org.openide.util.ImageUtilities;
public class DBGraphScene extends VMDGraphScene{
private static final Image IMAGE_LIST = ImageUtilities.loadImage("org/demo/resources/list_16.png"); // NOI18N
private static final Image IMAGE_CANVAS = ImageUtilities.loadImage("org/demo/resources/custom_displayable_16.png"); // NOI18N
private static final Image IMAGE_COMMAND = ImageUtilities.loadImage("org/demo/resources/command_16.png"); // NOI18N
private static final Image IMAGE_ITEM = ImageUtilities.loadImage("org/demo/resources/item_16.png"); // NOI18N
private static final Image GLYPH_PRE_CODE = ImageUtilities.loadImage("org/demo/resources/preCodeGlyph.png"); // NOI18N
private static final Image GLYPH_POST_CODE = ImageUtilities.loadImage("org/demo/resources/postCodeGlyph.png"); // NOI18N
private static final Image GLYPH_CANCEL = ImageUtilities.loadImage("org/demo/resources/cancelGlyph.png"); // NOI18N
private static int nodeID = 1;
private static int edgeID = 1;
public DBGraphScene() {
String mobile = createNode (this, 100, 100, IMAGE_LIST, "menu", "List", null);
createPin (this, mobile, "start", IMAGE_ITEM, "Start", "Element");
String game = createNode (this, 600, 100, IMAGE_CANVAS, "gameCanvas", "MyCanvas", Arrays.asList (GLYPH_PRE_CODE, GLYPH_CANCEL, GLYPH_POST_CODE));
createPin (this, game, "ok", IMAGE_COMMAND, "okCommand1", "Command");
createEdge (this, "start", game);
createEdge (this, "ok", mobile);
}
private static String createNode (VMDGraphScene scene, int x, int y, Image image, String name, String type, List<Image> glyphs) {
String nodeID = "node" + DBGraphScene.nodeID ++;
VMDNodeWidget widget = (VMDNodeWidget) scene.addNode (nodeID);
widget.setPreferredLocation (new Point (x, y));
widget.setNodeProperties (image, name, type, glyphs);
scene.addPin (nodeID, nodeID + VMDGraphScene.PIN_ID_DEFAULT_SUFFIX);
return nodeID;
}
private static void createPin (VMDGraphScene scene, String nodeID, String pinID, Image image, String name, String type) {
((VMDPinWidget) scene.addPin (nodeID, pinID)).setProperties (name, null);
}
private static void createEdge (VMDGraphScene scene, String sourcePinID, String targetNodeID) {
String edgeID = "edge" + DBGraphScene.edgeID ++;
scene.addEdge (edgeID);
scene.setEdgeSource (edgeID, sourcePinID);
scene.setEdgeTarget (edgeID, targetNodeID + VMDGraphScene.PIN_ID_DEFAULT_SUFFIX);
}
}
----
[start=4]
1. Add an instance variable for the scene to the top of the TopComponent's source code:
[source,java]
----
private DBGraphScene scene = new DBGraphScene();
----
Add the scene to the JScrollPane's ViewportView, at the end of the TopComponent's constructor:
[source,java]
----
jScrollPane1.setViewportView( scene.createView() );
----
[start=5]
1. Before continuining, check that the content of the filesystem tags in your layer.xml file is as follows:
[source,xml]
----
<filesystem>
<folder name="Actions">
<folder name="Window">
<file name="org-demo-DemoAction.instance">
<attr name="component" methodvalue="org.demo.DemoTopComponent.findInstance"/>
<attr name="displayName" bundlevalue="org.demo.Bundle#CTL_DemoAction"/>
<attr name="instanceCreate" methodvalue="org.openide.windows.TopComponent.openAction"/>
</file>
</folder>
</folder>
<folder name="Menu">
<folder name="Window">
<file name="DemoAction.shadow">
<attr name="originalFile" stringvalue="Actions/Window/org-demo-DemoAction.instance"/>
</file>
</folder>
</folder>
<folder name="Windows2">
<folder name="Components">
<file name="DemoTopComponent.settings" url="DemoTopComponentSettings.xml"/>
</folder>
<folder name="Modes">
<folder name="editor">
<file name="DemoTopComponent.wstcref" url="DemoTopComponentWstcref.xml"/>
</folder>
</folder>
</folder>
<folder name="Databases">
<folder name="Explorer">
<folder name="Connection">
<folder name="Actions">
<file name="org-demo-ShowDatabaseStructureAction.instance"/>
</folder>
</folder>
</folder>
</folder>
</filesystem>
----
[start=6]
1. Right-click the module and choose Run. Select "Demo" from the Window menu. The window component should open and show you the following:
image::images/db_69-db-2.png[]
== Connect the Database to the Scene
In this section, we connect to a selected database by using the Database Explorer API. We then pass the connection to the scene. In the scene, we parse the connection, extract the data, and visually display the data in the scene.
[start=1]
1. In your action class, you need to make a connection to a selected database and pass it to your window:
[source,java]
----
import java.sql.Connection;
import org.netbeans.api.db.explorer.ConnectionManager;
import org.netbeans.api.db.explorer.DatabaseConnection;
import org.netbeans.modules.db.explorer.action.BaseAction;
import org.openide.nodes.Node;
import org.openide.util.HelpCtx;
public final class ShowDatabaseStructureAction extends BaseAction {
@Override
protected void performAction(Node[] nodes) {
DatabaseConnection dbconn = nodes[0].getLookup().lookup(DatabaseConnection.class);
if (dbconn.getJDBCConnection() == null) {
ConnectionManager.getDefault().showConnectionDialog(dbconn);
}
Connection connection = dbconn.getJDBCConnection();
DemoTopComponent win = DemoTopComponent.findInstance();
win.open();
win.requestActive();
win.setConnection(connection);
}
@Override
protected boolean enable(Node[] activatedNodes) {
if (activatedNodes == null || activatedNodes.length != 1) {
return false;
}
boolean enabled = false;
DatabaseConnection dbconn = activatedNodes[0].getLookup().lookup(DatabaseConnection.class);
if (dbconn != null) {
enabled = true;
}
return enabled;
}
@Override
public String getName() {
return "Show Database Structure";
}
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
}
----
A red error underline will appear below the last line in the ``performAction`` , because it refers to a statement that we have not defined yet. We will do so in the next step.
[start=2]
1. Back in your window component, you need to receive the connection and call the scene, as shown below:
[source,java]
----
...
import java.sql.Connection;
...
final class DemoTopComponent extends TopComponent {
private static DemoTopComponent instance;
private static final String PREFERRED_ID = "DemoTopComponent";
private DBGraphScene scene;
private DemoTopComponent() {
initComponents();
setName(NbBundle.getMessage(DemoTopComponent.class, "CTL_DemoTopComponent"));
setToolTipText(NbBundle.getMessage(DemoTopComponent.class, "HINT_DemoTopComponent"));
}
public void setConnection(Connection connection){
scene = new DBGraphScene(connection);
jScrollPane1.setViewportView( scene.createView() );
}
...
...
...
----
In the scene, the constructor that receives the connection does not exist yet. We will create it in the next step. Until then, reference to that constructor, above, is underlined in red.
[start=3]
1. In the ``DBGraphScene`` class, extend the code as follows:
[source,java]
----
public class DBGraphScene extends VMDGraphScene {
private static final Image IMAGE_LIST = ImageUtilities.loadImage("org/demo/resources/list_16.png"); // NOI18N
private static final Image IMAGE_ITEM = ImageUtilities.loadImage("org/demo/resources/item_16.png"); // NOI18N
private static int edgeID = 1;
public DBGraphScene(Connection connection) {
try {
createSceneFromConnection(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
private void createSceneFromConnection(Connection jdbcConnection) throws SQLException {
ArrayList<String> tables = new ArrayList<String>();
DatabaseMetaData databaseMetaData = jdbcConnection.getMetaData();
String[] names = {"TABLE"};
ResultSet resultSet = databaseMetaData.getTables(null, "%", "%", names);
while (resultSet.next()) {
String table = resultSet.getString("TABLE_NAME");
tables.add(table);
createNode(this, (int) (Math.random() * 800), (int) (Math.random() * 800), IMAGE_LIST, table, "Table", null);
ResultSet columns = jdbcConnection.getMetaData().getColumns(null, null, table.toUpperCase(), "%");
while (columns.next()) {
String columnName = columns.getString("COLUMN_NAME");
createPin(this, table, table + ":" + columnName, IMAGE_ITEM, columnName, columnName);
}
}
for (String string : tables) {
ResultSet resultSet1 = databaseMetaData.getExportedKeys(null, null, string);
while (resultSet1.next()) {
String pkTable = resultSet1.getString("PKTABLE_NAME");
String pkColumn = resultSet1.getString("PKCOLUMN_NAME");
String fkTable = resultSet1.getString("FKTABLE_NAME");
String fkColumn = resultSet1.getString("FKCOLUMN_NAME");
createEdge(this, fkTable + ":" + fkColumn, pkTable + ":" + pkColumn);
}
}
this.moveTo(null);
}
private static String createNode(VMDGraphScene scene, int x, int y, Image image, String name, String type, java.util.List<Image> glyphs) {
String node = name;
VMDNodeWidget widget = (VMDNodeWidget) scene.addNode(node);
widget.setPreferredLocation(new Point(x, y));
widget.setNodeProperties(image, name, type, glyphs);
return node;
}
private static void createPin(VMDGraphScene scene, String nodeID, String pinID, Image image, String name, String type) {
((VMDPinWidget) scene.addPin(nodeID, pinID)).setProperties(name, null);
}
private static void createEdge(VMDGraphScene scene, String sourcePinID, String targetPinID) {
String edge = "edge" + DBGraphScene.edgeID++;
scene.addEdge(edge);
scene.setEdgeSource(edge, sourcePinID);
scene.setEdgeTarget(edge, targetPinID);
}
private void moveTo(Point point) {
int index = 0;
for (String node : getNodes()) {
getSceneAnimator().animatePreferredLocation(findWidget(node), point != null ? point : new Point(++index * 100, index * 100));
}
}
}
----
The ``createSceneFromConnection`` method gets the ``DatabaseMetadata ( jdbcConnection.getMetaData() )`` from the ``Connection`` . With the subsequent calls to ``getTables`` , the table structure is retrieved and the nodes of the graph are created. For every table, ``getColumns`` is called and a Pin is added for every column to the table node. These pins can be used in a subsequent step to create a connection between two tables. Now we iterate through the tables and call ``getExportedKeys`` to get hold of the ``ForeignKeys`` . For every exported key, an edge is created between the pins of the related columns. The ``moveTo`` method does the animation when the window is opened.
[start=4]
1. Run the module again. Open the Services window. Expand the Databases node. Select one of the connection nodes. The "Show Database Structure" menu item, under the File menu, should now be enabled. Select it and now your scene should open and look as follows:
image::images/db_69-db-1.png[]
Congratulations, you have completed a Visual Library scene that connects to a database and visually displays its content.
link:http://netbeans.apache.org/community/mailing-lists.html[Send Us Your Feedback]
[[
== Next Steps
]]
For more information on working with the Visual Library API, see:
* link:http://www.javalobby.org/eps/netbeans_visual_library/[Roman Strobl's Visual Library Screencast] on Javalobby.
* link:https://netbeans.apache.org/graph/[Visual Library Project Page]
* link:https://netbeans.apache.org/graph/documentation.html[Visual Library 2.0 - Documentation]
* link:http://bits.netbeans.org/dev/javadoc/org-netbeans-api-visual/overview-summary.html[Visual Library API Javadoc]
* link:http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-db/overview-summary.html[Database Explorer API Javadoc]
With thanks to Toni Epple, who wrote the first version of this tutorial, link:http://wiki.netbeans.org/VisualDatabaseExplorer[A Visual Database Explorer for NetBeans].