Table of Contents
Based on the information presented in the previous chapters, we can combine everything into an examples. In this chapter, we present the step-by-step process for using the Tweek Java and C++ APIs.
In this example, we explain how to develop a simple Tweek interface. The goal is to have a “collaborative” slider in a Java GUI component. The value displayed by the slider is retained by a C++ application so that multiple independent sliders can show the same value. The steps explained here are highly representative of the normal steps to be followed when using the Tweek Java and C++ APIs. The structure of the following sections lays out the order of the steps taken. An example makefile that goes along with the code presented can be found in the section called “SliderSubject” of Appendix A, Compiling Example Code. The full source for the examples presented in this section can be found in $TWEEK_BASE_DIR/share/test/NetworkTestBean.
To begin, the subject interface must be defined in IDL and implemented in C++. The interface itself will be “compiled” into Java and C++ code. Both ends of the communication channels must know the interface in order for the references to be used, thus requiring the generation of code for both languages.
Creating an IDL interface involves writing an IDL file. For this example, we will be storing an integer variable in a C++ servant. The Java GUIs will need to read and write the value, so we need two methods: getValue() and setValue(). The type being passed between ORBs will be long, a 32-bit integer. Depending on the target language, this will map to the corresponding type of the same size.
Example 6.1. SliderSubject.idl
1 #ifndef _NETWORK_TEST_SLIDER_SUBJECT_IDL_#define _NETWORK_TEST_SLIDER_SUBJECT_IDL_
#include <tweek/idl/Subject.idl>
5 module networktest
{ interface SliderSubject : tweek::Subject
{ 10 void setValue(in long val);
long getValue();
}; }; 15 #endif
![]()
The file SliderSubject.idl must be “compiled” by an IDL compiler. For use with Tweek, the interface must be compiled into Java and C++ code. The generated Java code will be used solely for communicating with CORBA networktest.SliderSubject references. The generated C++ code will be extended to provide an implementation of the networktest::SliderSubject interface. (The implementation will be a CORBA servant object to which references will be made by remote Java code.)
After running an IDL compiler to generate the stub CORBA code, the interface must be implemented. In particular, there will be pure virtual methods in SliderSubject.h that must be implemented. The implementing class will be the CORBA servant holding the actual data visualized in the Java GUI slider.
Example 6.2. SliderSubjectImpl.h
1 #ifndef _SLIDER_SUBJECT_IMPL_H_ #define _SLIDER_SUBJECT_IMPL_H_ #include <tweek/tweekConfig.h> 5 #include <vector> #include <tweek/CORBA/SubjectImpl.h>#include <SliderSubject.h>
10 namespace networktest
{ /** 15 * This class is an extension to the base Tweek SubjectImpl class. It uses * multiple inheritance with that class and with the generated CORBA class * corresponding to the IDL for SliderSubject. */ class SliderSubjectImpl : public POA_networktest::SliderSubject,
20 public tweek::SubjectImpl { public: SliderSubjectImpl() : tweek::SubjectImpl(), mValue(0) 25 { /* Do nothing. */ ; } virtual ~SliderSubjectImpl() 30 { /* Do nothing. */ ; } /** 35 * Sets this subject's internal value. */ virtual void setValue(long value);
/** 40 * Returns this subject's internal value. */ virtual long getValue();
/** 45 * This overriding method is needed so that the correct type is returned * when the _this() method is invoked. Without this method, an object of * type tweek::Subject_ptr would be returned. * * XXX: It may be possible to remove this requirement in the future. 50 */ networktest::SliderSubject_ptr _this()
{ return POA_networktest::SliderSubject::_this(); } 55 private: long mValue; /**< Our value */
}; 60 } // End of networktest namespace #endif /* _SLIDER_SUBJECT_IMPL_H_ */
| These files will always be included by implementations of Tweek subject derived classes. The first contains the declaration for the basic Tweek subject implementation. The second contains the C++ code generated from the Tweek Observer.idl file. |
| This header is generated by the IDL compiler from SliderSubject.idl. In particular, it defines the class from which networktest::SliderSubjectImpl must inherit. |
| In SliderSubject.idl, shown in Example 6.1, “SliderSubject.idl”, the interface is in the networktest module. In the C++ implementation code, the module name corresponds to a namespace. |
| The interface implementation class must inherit from the IDL-generated class POA_networktest::SliderSubject and from tweek::SubjectImpl. |
| POA_networktest::SliderSubject defines two pure virtual methods that must be implemented. These correspond to the methods in SliderSubject.idl. |
| As of this writing, all subject implementations must contain an overriding version of the _this() method. This is due to the use of multiple inheritance. Note the return type and the return statement. These will vary for each subject implementation based on the name of the IDL-defined interface. |
| This is the actual value being stored by the C++ servant. |
In SliderSubjectImpl.h, the most important parts to note are the use of multiple inheritance, the declarations of the SliderSubject interface methods, and the implementation of _this(). The implementations of setValue() and getValue() are shown next in Example 6.3, “SliderSubjectImpl.cpp”.
Example 6.3. SliderSubjectImpl.cpp
1 #include <vpr/Util/Debug.h> #include <SliderSubjectImpl.h>namespace networktest 5 { void SliderSubjectImpl::setValue(long value) { mValue = value;
10 // Notify any observers that our value has changed. This is very // important. tweek::SubjectImpl::notify();
} 15 long SliderSubjectImpl::getValue() { return mValue;
} 20 } // End networktest namespace
| Include the class declaration file, shown in Example 6.2, “SliderSubjectImpl.h”. |
| When invoked, the remote caller will pass a long value, and this saves the result into the servant's storage. |
| Because the subject's state has been modified, all attached observers must be notified. This is a very important step that must be taken in this method. Note that it invokes the notify() method of the parent class. |
| Observers will invoke this method when requesting the current mValue. |
The observer does not define its own specialized IDL interface. Instead, it makes use of the existing Tweek basic observer (tweek.ObserverPOA in Java). The method update() must be implemented. The remainder of the observer implementation is centered around communication with a SliderSubject object reference. All observer code is written in Java. The only C++ code for observers is part of the Tweek library, and it is generated entirely by the IDL compiler.
For every subject interface defined in IDL, a corresponding observer class must be written in Java. Without an observer, there is no way for the Java and C++ sides to conduct useful two-way communication. At best, the Java GUI could request a subject reference and manipulate the C++ application through the reference, but the communication would be entirely one-way.
In Example 6.4, “SliderObserverImpl.java”, we show the complete Java implementation of an observer corresponding to the tweek::SliderSubject interface defined earlier. (The JavaBean that uses this observer is explained in the section called “The JavaBean”.) The main focus of this observer is to update its contained JSlider whenever the state of the corresponding subject changes.
Example 6.4. SliderObserverImpl.java
1 package networktest; import javax.swing.DefaultBoundedRangeModel; import javax.swing.JSlider; 5 import tweek.*; /** * Implementation of the Observer side of the Tweek Subject/Observer pattern. * It must extend tweek.ObserverPOA so that instances of this class can be 10 * registered as CORBA servants. In addition, CORBA references to the * servants must be capable of being attached to remote subjects. */ public class SliderObserverImpl extends ObserverPOA{ 15 public SliderObserverImpl(JSlider slider, SliderSubject subject)
{ mSlider = slider; mSliderSubject = subject; } 20 /** * Implements the required method in tweek.ObserverPOA. The remote subject * will invoke this method whenever it is notified of a change. */ 25 public void update()
{ // If we have a valid slider object, we need to update its value to // whatever our subject has. if ( mSlider != null ) 30 { DefaultBoundedRangeModel model = (DefaultBoundedRangeModel) mSlider.getModel(); model.setValue(mSliderSubject.getValue());
mSlider.repaint(); 35 } } /** * Detaches this observer from our subject. This is needed when shutting 40 * down a CORBA connection. */ public void detach() { mSliderSubject.detach(this._this());
45 } private SliderSubject mSliderSubject = null; private JSlider mSlider = null; }
| As an observer, this class must derive from tweek.ObserverPOA. This class is generated by the IDL compiler and is part of the Tweek Network Library (see the section called “Network Library”). |
| The constructor for this observer takes two arguments: a JSlider object reference and a networktest.SliderSubject object reference. The observer needs the latter argument so that it can query state information from the subject when notified of state changes. This is part of the subject/observer design pattern [Gam95]. |
| As stated, all observers must implement update(). This will be invoked by the remote subject when its notify() method is invoked. |
| To get the updated state of the remote subject, the encapsulated subject reference's getValue() method is invoked. The value returned will be the most up-to-date information from the subject. |
| It may be convenient for the observer to implement a detach() method (though the name may vary). This is used when the Java application is shutting down to ensure that the remote subject does not have dangling references to observers that no longer exist. The only action required here is invoking the subject's detach() method to inform the subject that this observer is going away. |
This example demonstrates that observers do not have to be complex to be usable. While this example is purposely simple, it should illustrate that developers of observers do not necessarily have to make their implementations complicated. As will be shown in the section called “The JavaBean”, the JavaBean that uses this observer completes the picture and provides users with a GUI slider that can be manipulated by any number of simultaneous users.
Now that we have the subject and observer ready to go, we can make an application that uses them. The following example is a (relatively) simple C++ application that starts the CORBA Manager, creates the Subject Manager, registers a networktest::SliderSubject servant, and then waits for the user to press 'x' to exit. The use of exceptions may appear unfamiliar to some C++ programmers. CORBA makes use of exceptions as a cross-language mechanism to report errors, and thus, there must be proper exception handling code for the application to work correctly.
Example 6.5. SliderSubjectApp.cpp
1 #include <tweek/CORBA/CorbaManager.h>#include <vpr/Thread/Thread.h> #include <vpr/Util/Debug.h> 5 #include <SliderSubjectImpl.h>
/** * This application starts the CORBA server for the C++ side of the test. */ 10 int main(int argc, char* argv[]) { tweek::CorbaManager mgr;
// The first thing we have to do is initialize the Tweek CORBA Manager. 15 // If this fails, we're out of luck. try { if ( mgr.init("corba_test", argc, argv).success() )
{ 20 vpr::ReturnStatus status; // Once the CORBA Manager is initialized, we need to create a // Subject Manager. This will hold our SliderSubject object. try 25 { status = mgr.createSubjectManager();
// If we were able to create the Subject Manager, now we register // our objects with it. 30 if ( status.success() ) { // First, create real instances of the C++ object that will // be the CORBA servant. This must be allocated on the heap. networktest::SliderSubjectImpl* slider_subj = 35 new networktest::SliderSubjectImpl();
// Now we try to register the subject and give it a symbolic, // easy-to-remember name. try 40 { mgr.getSubjectManager()->registerSubject(slider_subj, "SliderSubject");
} catch (...) 45 { vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL) << "Failed to register subject\n" << vprDEBUG_FLUSH; } } 50 } catch (CORBA::Exception& ex) { vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL) << "Caught an unknown CORBA exception when trying to register!\n" 55 << vprDEBUG_FLUSH; } if ( ! status.success() ) { 60 vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL) << "Failed to register Subject Manager instance\n" << vprDEBUG_FLUSH; } 65 std::cout << "Press 'x' to exit" << std::endl; char input; // Loop forever so that we can act sort of like a server. while ( 1 )
70 { std::cin >> input; if ( input == 'x' ) { break; 75 } else { vpr::System::msleep(100); } 80 } } else { vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL) 85 << "CORBA failed to initialize\n" << vprDEBUG_FLUSH; } } catch (...) { 90 vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL) << "Caught an unknown exception!\n" << vprDEBUG_FLUSH; } vprDEBUG(vprDBG_ALL, vprDBG_CRITICAL_LVL) << "Exiting\n" << vprDEBUG_FLUSH; 95 return 0; }
| These two headers are typically needed. The first includes the declaration of the Tweek CORBA Manager, and the second is the subject implementation declaration, shown in Example 6.2, “SliderSubjectImpl.h”. |
| In order to use CORBA through Tweek, the CORBA Manager must be created and initialized. Any number of these may be created, but in general, only one is needed per application |
| After the COBRA Manager has been initialized successfully, the Tweek Subject Manager must be created. |
| Once we have a valid Subject Manager, we must register subjects with it in order for object references to be passed out by CORBA. This creates the servant to which networktest::SliderSubject references will be made. |
| Once the servant is created, it is registered with the Subject Manager. The Subject Manager will activate the servant within the POA so that references to it can be created and returned to clients. |
| After the subject is registered, all the work is done. This application now just waits for clients to request references. It will exit when the user enters 'x'. |
The application shown in Example 6.5, “SliderSubjectApp.cpp” is purposely simple. There are many ways to use the CORBA Manager and the Subject Manager. For example, an object registry could be built on top of the Subject Manager so that only specific types of servant objects may be registered. Servant registration could be automated in object constructors. The C++ API is intended to be simple to enhance its usability and flexibility, and the application shown in the example is just that: an example.
We have finally reached the point at which we implement a JavaBean that can visualize the numeric data held by the C++ slider subject. Such a JavaBean must be defined as a Tweek Panel Bean. (Refer back to Chapter 2, JavaBeans if these statements seem unfamiliar or confusing.)
The JavaBean shown in the following example is typical of one that uses the Tweek Network library to take advantage of CORBA facilities. The code for the Bean is longer than previous examples, and because of this, it will be split into multiple code blocks. Each will be discussed in turn. The full code is in one file: NetworkTest.java.
Please note that the details of setting up the GUI elements used by the Bean are left out. The code in this case was generated by JBuilder and could easily vary from Bean to Bean. For the full code, please refer to the aforementioned locations.
The slider JavaBean uses common Java Swing classes, a CORBA exception class, the Tweek Event library, the Tweek Network library, and the Java code generated by an IDL compiler. The following explains how each of these are imported into the main Bean class.
package networktest;import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import org.omg.CORBA.BAD_PARAM;
import org.vrjuggler.tweek.event.*;
import org.vrjuggler.tweek.net.*;
import org.vrjuggler.tweek.net.corba.*;
import tweek.*;
![]()
Now we can begin writing the Bean code. Besides the class SliderObserverImpl, this Bean has only one class: NetworkTest. It will provide the GUI representation of the numeric data. The declaration of the class follows.
/** * This is an example of a JavaBean that Tweek can load dynamically. It holds * a JSlider that acts as an Observer in the Tweek CORBA Subject/Observer * pattern implementation. * * @version 1.0 */ public class NetworkTest extends JPanelimplements CommunicationListener, TweekFrameListener
{ ... }
Before delving into the methods of the networktest.NetworkTest class, it will be helpful to review the member variables used throughout the class. Refer back to this section if there is any confusion regarding the use or the type of some member variable in the method implementations.
private BorderLayout mBeanLayout = new BorderLayout();private JPanel mSliderPanel = new JPanel();
private JSlider mDataSlider = new JSlider();
private SliderObserverImpl mSliderObserver = null;
![]()
Note that the observer is stored in a member variable initialized to null. It will be assigned a value when a CORBA service becomes available and the corresponding subject can be requested. The object itself is stored as a member variable so that it can be accessed by all the methods of the class.
The most complex part of this Bean is the handling of CORBA communication events delivered by the Tweek GUI. All of the handling in this example will be done in the methods connectionOpened() and connectionClosed(). The steps that must be followed are straightforward, but there are errors that must be handled properly. It is the error handling that can make the code look daunting, not the use of the Tweek Network library.
1 /** * Implements the Tweek CommunicationListener interface needed for being * informed of new connections with remote ORBs. */ 5 public void connectionOpened(CommunicationEvent e){ // The first thing to do is get the CORBA service object from the // event. We need this so we know to whom we are are connecting. Once // we have the CORBA service, we get its Subject Manager since that's 10 // what contains the actual subjects we need. CorbaService corba_service = e.getCorbaService();
SubjectManager mgr = corba_service.getSubjectManager();
Subject subject = mgr.getSubject("SliderSubject");
15 SliderSubject slider_subject = null; // Try to narrow the Subject object to a SliderSubject object. If this // fails, it throws a CORBA BAD_PARAM exception. In that case, we open // a dialog box saying that the narrowing failed. 20 try { slider_subject = SliderSubjectHelper.narrow(subject);
} catch (BAD_PARAM narrow_ex)
25 { JOptionPane.showMessageDialog(null, "Failed to narrow subject to SliderSubject", "SliderSubject Narrow Error", JOptionPane.ERROR_MESSAGE); 30 } // Ensure that slider_subject is a valid object just to be safe. if ( slider_subject != null ) { 35 // First, we need a Java object that implements the Observer. That // object must be registered with the Java CORBA service. m_slider_observer = new SliderObserverImpl(mDataSlider, slider_subject);
corba_service.registerObject(mSliderObserver, "SliderObserver");
40 // Now that the observer is registered, we can attach it to the // subject. The subject needs to know who its observers are so // that it can notify them of updates. slider_subject.attach(mSliderObserver._this());
45 // Now we set the slider in our GUI to be whatever value the // remote subject is holding for us. mDataSlider.setValue(slider_subject.getValue());
mDataSlider.addChangeListener( 50 new SliderChangeListener(slider_subject) ); (11) } } 55 /** * Implements the Tweek CommunicationListener interface needed for being * informed when existing ORB connections are closed. */ public void connectionClosed(ConnectEvent e) (12) 60 { if ( mSliderObserver != null ) { mSliderObserver.detach(); (13) mSliderObserver = null; 65 } }
| If the event delivered to the Bean is a newly opened connection, there is a new CORBA service available, so we will create a new observer to communicate through that service. |
| The first step that must be taken is retrieving the org.vrjuggler.tweek.net.corba.CorbaService object from the event. This is needed as the basis for all further actions. With the CORBA service reference, we can request the Subject Manager reference. The result is a CORBA reference to the remote Subject Manager, a servant within the C++ application. |
| Through the Subject Manager, we request subjects using symbolic names. In this case, we request the subject with the hard-coded name “SliderSbuject”. |
| The call to getSubject() returns a reference of type tweek.Subject, but we need to narrow it to networktest.SliderSubject so we can use it. Among the code generated by the IDL compiler is a class named networktest.SliderSubjectHelper. Its narrow() method is used to narrow the CORBA reference type to a more specific type. If the narrowing fails, an exception of type org.omg.CORBA.BAD_PARAM is thrown, and it is best to handle it here. |
| Once we have the subject reference, we create an observer servant that we will attach to the subject. This is where networktest.SliderObserverImpl comes into the picture. |
| The observer servant must be registered with the local CORBA service so that references to it can be created. This single statement takes care of all the POA activation behind the scenes. |
| Now that the servant is registered with the CORBA service, a reference to it can be passed to the remote subject in order to attach the observer to the subject. |
| Once the initial references are passed around, we need to update the GUI slider to the current value held by the subject. |
| (11) | As part of the collaborative slider, we need to register a change listener with the GUI slider so that it will be informed of changes made by the local user. These changes are reported to the remote subject so that other users get the update. The class SliderChangeListener is shown later. |
| (12) (13) | If the event delivered to the Bean was the closing of an existing connection, the CORBA service is being shut down. When the CORBA service is shut down, we need to detach the local observer from the remote subject to prevent further update notification attempts for this observer. |
It is important to note that the use of hard-coded subject names is not recommended. In this example, the subject name is hard-coded for simplicity. The Tweek Subject Manager allows accessing code to request a list of all registered subjects. Using this information, it is possible to present the Java GUI user with a list of subjects from which they can make a selection.
This Bean makes an effort to shut down open CORBA connections when the Tweek GUI is closed. It does this by implementing org.vrjuggler.tweek.event.TweekFrameListener which has a single method, frameStateChanged().
public void frameStateChanged(TweekFrameEvent e)
{
if ( e.getType() == TweekFrameEvent.FRAME_CLOSE )
{
if ( mSliderObserver != null )
{
mSliderObserver.detach();
mSliderObserver = null;
}
}
}A helper class is used to handle local change events in the slider. The class is a private inner class within the Bean and is defined as follows:
private class SliderChangeListener implements ChangeListener
{
public SliderChangeListener(SliderSubject subject)
{
mSliderSubject = subject;
}
public void stateChanged(javax.swing.event.ChangeEvent e)
{
JSlider source = (JSlider) e.getSource();
if ( ! source.getValueIsAdjusting() )
{
mSliderSubject.setValue(source.getValue());
}
}
private SliderSubject mSliderSubject = null;
}With the Bean implementation done, the XML file that describes the Bean must be written. All Beans loaded by the Tweek Java GUI are described by an XML file. In Example 6.6, “NetworkTestBean.xml”, we show the XML file for the Bean we have been developing.
Example 6.6. NetworkTestBean.xml
<?xml version="1.0" encoding="UTF-8"?><beanlist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.vrjuggler.org/tweek/xsd/1.1/beanlist.xsd"> <guipanel name="Network Tester">
<file name="${TWEEK_BASE_DIR}/bin/beans/NetworkTestBean.jar"
class="networktest.NetworkTest" /> <tree path="/Beans" />
</guipanel> </beanlist>
After the Bean is compiled into a JAR file, the JAR file and XML file need to be copied into $TWEEK_BASE_DIR/bin/beans. This is the default path that Tweek searches for Beans at startup, and it is a convenient place to put this example Bean.
With all the coding done and the code compiled, we can run the C++ application and connect multiple instances of the Tweek Java GUI to it. The steps to run the C++ application are as follows:
Run a CORBA Naming Service. This is required so that the C++ and Java ORBs can resolve symbolic references.
Since Tweek uses omniORB for a C++ ORB (see Appendix B, CORBA Implementations for more information on this), the environment variable $OMNIORB_CONFIG must be set. This gives the full path to an omniORB configuration file that omniORB will load at runtime.
Run the C++ application.
Run the Tweek Java GUI. This will find and load all the Beans in $TWEEK_BASE_DIR/bin/beans.
Within the Java GUI, connect to the CORBA Naming Service started in step 1. The way the Bean is written, it will request the subject held by the C++ application automatically, and the slider will be updated to the current value.