Thursday, March 5, 2009

Quick Tips: Storing preferences and settings per user in a RCP application

Eclipse RCP applications store the prefrences and settings in the workspace. When a RCP application is exported, the workspace is opened within the application folder unless the workspace location is overridden. This will make every user who uses the application to use the same preferences. To enable the preferences and workbench state to be user specific, the default location of the workspace can be configured in the config.ini file as follows.


osgi.instance.area.default=@user.home/eclipseworkspace


Now the preferences and other dialog settings will be written in the user's own workspaces.

Quick Tip: Save and Restore the perspective layout

To restore the perspctive to the last modified state, eclipse stores the workbench state to a file called workbench.xml. To enable the save and restore layout, the IWorkbenchConfigurer must be set in the ApplicationWorkbenchAdvisor.


public void initialize(IWindowConfigurer configurer)
{
configurer.setSaveAndRestore(true);
}

Monday, March 2, 2009

Eclipse : Writing to the console in a RCP Application

When you want to add console message to the eclipse console view, there is no easiest way to do this. The sysout/syserr messages will go on the console where you have the application if you run it with -consoleLog option.

How do we show the messages on the console view eclipse provides. First of all we have to add the console view to the perspective. The console view is part of the org.eclipse.ui.console plugin, so add it to the dependencies if you dont have it already.

Now add the ConsoleView to the perspective in the createInitialLayout method ( the following snippet is written with the RCP Mail example )


public void createInitialLayout(IPageLayout layout) {
String editorArea = layout.getEditorArea();
layout.setEditorAreaVisible(false);

layout.addStandaloneView(NavigationView.ID, false, IPageLayout.LEFT,
0.25f, editorArea);
IFolderLayout folder = layout.createFolder("messages", IPageLayout.TOP,
0.5f, editorArea);
folder.addPlaceholder(View.ID + ":*");
folder.addView(View.ID);

IFolderLayout consoleFolder = layout.createFolder("console",
IPageLayout.BOTTOM, 0.65f, "messages");
consoleFolder.addView(IConsoleConstants.ID_CONSOLE_VIEW);
layout.getViewLayout(NavigationView.ID).setCloseable(false);
}


Now if you run the application you can see the console view at the bottom of the Messages view of the RCP Mail application. But now there are no open consoles on the console view. Now let us take an example, whenever a Messages view is opened we want to write something to the console.


public class OpenViewAction extends Action {

private final IWorkbenchWindow window;
private int instanceNum = 0;
private final String viewId;
MessageConsole messageConsole;

public OpenViewAction(IWorkbenchWindow window, String label, String viewId) {
this.window = window;
this.viewId = viewId;
setText(label);
// The id is used to refer to the action in a menu or toolbar
setId(ICommandIds.CMD_OPEN);
// Associate the action with a pre-defined command, to allow key
// bindings.
setActionDefinitionId(ICommandIds.CMD_OPEN);
setImageDescriptor(com.blog.sample.Activator
.getImageDescriptor("/icons/sample2.gif"));

}

public void run() {
if (window != null) {
try {
int instance = instanceNum++;
window.getActivePage().showView(viewId,
Integer.toString(instance),
IWorkbenchPage.VIEW_ACTIVATE);

messageConsole = getMessageConsole();
MessageConsoleStream msgConsoleStream = messageConsole
.newMessageStream();

ConsolePlugin.getDefault().getConsoleManager().addConsoles(
new IConsole[] { messageConsole });

msgConsoleStream.println(viewId + Integer.toString(instance));

} catch (PartInitException e) {
MessageDialog.openError(window.getShell(), "Error",
"Error opening view:" + e.getMessage());
}
}
}

private MessageConsole getMessageConsole() {
if (messageConsole == null) {
messageConsole = new MessageConsole("RCPMail", null);
ConsolePlugin.getDefault().getConsoleManager().addConsoles(
new IConsole[] { messageConsole });
}

return messageConsole;
}

}


Now every new view opened will write the ViewID to the RCPMail console we created.

Have fun!

Thursday, February 26, 2009

Quick Tip : Adding SWT controls to the Trim Area

Sometimes RCP applications needs to show a label or some swt control on the trim area (outer boundary of the workbench window ) for example in the status area, after the perspective switch bar area and so on.

org.eclipse.ui.menus extension point allows to places command or control on the toolbar , menubar or on the trim area. For this post, if you want to add a control or command to the status area or the perspective switch area, use the following locationURI.

For placing it on the status area - toolbar:org.eclipse.ui.trim.status this will place the control or command on the status area. ( You have to make sure the status area is made visible in the workbenchwindowadvisors preWindowOpen() method as


public void preWindowOpen() {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer.setInitialSize(new Point(600, 400));
configurer.setShowCoolBar(true);
configurer.setShowPerspectiveBar(true);
configurer.setShowStatusLine(true);
}


For placing it on the perspective switch area - toolbar:org.eclipse.ui.trim.command2, this will add the control next to the perspective switch area.

The extension point should create a menuContribution with the correct locationURI, and add the controls/commands to a toolbar.


<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="toolbar:org.eclipse.ui.trim.command2">
<toolbar
id="com.blog.sample.toolbar1">
<control
class="com.blog.sample.WorkbenchWindowControlContribution1">
</control>
</toolbar>
</menuContribution>
</extension>


Have fun !

Tuesday, February 24, 2009

Eclipse : Minimizing the RCP App to the system tray.

ApplicationWorkbenchWindowAdvisor of the RCP application can handle preWindowShellClose() and postWindowClose(). If you want the application to be stored to the system tray when closed, the preWindowClose event can be handled as below to add a system tray icon with Open and Exit actions which will open/exit the workbench.


@Override
public boolean preWindowShellClose() {
final TrayItem item = new TrayItem(
Display.getCurrent().getSystemTray(), SWT.NONE);
final Image image = Activator.getImageDescriptor("icons/mail.ico")
.createImage();
item.setImage(image);
item.setToolTipText("RCPMail - Tray Icon");
getWindowConfigurer().getWindow().getShell().setVisible(false);
item.addSelectionListener(new SelectionAdapter() {
public void widgetDefaultSelected(SelectionEvent e) {
Shell workbenchWindowShell = getWindowConfigurer().getWindow()
.getShell();
workbenchWindowShell.setVisible(true);
workbenchWindowShell.setActive();
workbenchWindowShell.setFocus();
workbenchWindowShell.setMinimized(false);
image.dispose();
item.dispose();
}
});

Shell workbenchWindowShell = getWindowConfigurer().getWindow()
.getShell();
// Create a Menu
final Menu menu = new Menu(workbenchWindowShell, SWT.POP_UP);
// Create the exit menu item.
final MenuItem exit = new MenuItem(menu, SWT.PUSH);
exit.setText("Exit");

// Create the open menu item.
final MenuItem open = new MenuItem(menu, SWT.PUSH);
open.setText("Open");
// make the workbench visible in the event handler for exit menu item.
open.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
// Do a workbench close in the event handler for exit menu item.
exit.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
image.dispose();
item.dispose();
open.dispose();
exit.dispose();
menu.dispose();
getWindowConfigurer().getWorkbenchConfigurer().getWorkbench()
.close();
}
}); Shell workbenchWindowShell = getWindowConfigurer().getWindow()
.getShell();
workbenchWindowShell.setVisible(true);
workbenchWindowShell.setActive();
workbenchWindowShell.setFocus();
workbenchWindowShell.setMinimized(false);
image.dispose();
item.dispose();
open.dispose();
exit.dispose();
menu.dispose();
}
});
item.addListener(SWT.MenuDetect, new Listener() {
public void handleEvent(Event event) {
menu.setVisible(true);
}
});
// Do a workbench close in the event handler for exit menu item.
exit.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
image.dispose();
item.dispose();
open.dispose();
exit.dispose();
menu.dispose();
getWindowConfigurer().getWorkbenchConfigurer().getWorkbench()
.close();
}
});
return false;
}

Equinox : Authenticate with Database Login using Equinox Security

Most of the desktop applications require a authentication step when the application is started. Eclipse does not yet provide a login mechanism to authenticate the user. But the equinox security has it on the proposal to support login dialog based authentication. Till then, what would be the best way to do the authentication with existing equinox security features. Lets have a look at RCP Mail Template with a login dialog during start up.

Equinox implements the standard Java Authentication and Authorization Service ( JAAS ) to authenticate and authorize. We will look at some realtime examples of how to do the authentication.

Equinox supports extension login modules which can be configured via the loginmodule extension. The extension can be configured with nt login module, LDAP login modules from the sun security modules or can be configured with custom written LoginModules like RDBMSLoginModule, SecureStorage Logins and so on.

The example from the org.eclipse.equinox.security.sample from the equinox cvs (org.eclipse.equinox/incubator/security/bundles/org.eclipse.equinox.security.sample ) /cvsroot/rt explains adding LDAP, WIN32 and other login modules to the application. Here in this example we will write a custom LoginModule which authenticates against a database connection.

RDBMSLoginModule - The login modules has to implement LoginModule interface from javax security. The LoginModule contains initialize, login, logout, commit and abort methods. The initialize method gives the CallbackHanlder and other options.

The Login Method which does the authentication has to be implemented against a database for this example.

public boolean login() throws LoginException
{

if (callbackHandler == null)
throw new LoginException("Error: no CallbackHandler available "
+ "to garner authentication information from the user");

try
{
// Setup default callback handlers.
Callback[] callbacks = new Callback[] { new NameCallback("Username: "),
new PasswordCallback("Password: ", false), new NameCallback("Database: ") };

callbackHandler.handle(callbacks);

String username = ((NameCallback) callbacks[0]).getName();
String password = new String(((PasswordCallback) callbacks[1]).getPassword());
String dbname = ((NameCallback) callbacks[2]).getName();

((PasswordCallback) callbacks[1]).clearPassword();

success = rdbmsValidate(username, password,dbname); // This method should try to connect
//to the database with the given username,password, url and return true on success.

callbacks[0] = null;
callbacks[1] = null;

if (!success)
throw new LoginException("Authentication failed: Password does not match");

return (true);
}
catch (LoginException ex)
{
throw ex;
}
catch (Exception ex)
{
success = false;
throw new LoginException(ex.getMessage());
}
}
The LoginModule is declared with org.eclipse.equinox.security.loginModule extension point.


<extension
id="com.sample.login.RdbmsLoginModule"
point="org.eclipse.equinox.security.loginModule">
<loginModule
class="com.sample.login.RdbmsLoginModule">
</loginModule>
</extension>

The login method calls the callback handler which is configured in the plugin.xml via the extension org.eclipse.equinox.security.callbackHandler

<extension
id="com.sample.login.LoginDialogCallbackHandler"
point="org.eclipse.equinox.security.callbackHandler">
<callbackHandler
class="com.sample.login.LoginDialogCallbackHandler">
</callbackhandler>
</extension>


The call back handler is mapped to a configName in this case say RDBMS to a Dialog which takes username, password and the db url.

The CallBackHandlerMapping is done as follows

<extension
point="org.eclipse.equinox.security.callbackHandlerMapping">
<callbackHandlerMapping
callbackHandlerId="com.sample.login.LoginDialogCallbackHandler"
configName="RDBMS">
</callbackHandlerMapping>
</extension>

Once the rdbmsValidate method successfully connects to the database with the given values , a javax.security.auth.Subject can be constructed with the Credentials and Principals as an authenticated user.

private boolean rdbmsValidate(String user, String pass) throws Exception
{

Connection con;
boolean passwordMatch = true;

try
{
Class.forName(driverClass);
}
catch (java.lang.ClassNotFoundException e)
{
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
throw new LoginException("Database driver class not found: " + driverClass);
}

try
{
if (debug)
System.out.println("\t\t[RdbmsLoginModule] Trying to connect...");

con = DriverManager.getConnection(url, user, pass);
if(con == null )
passwordMatch = false;
else
//Construct a subject.
} catch(Exception ex) {
}
return (passwordMatch)'
}

Now that all the extension points are made for the authentication. We need to call the LoginModule at the appropriate place on the application. Usually the Eclipse Application start method is a good place to keep the Authentication process.

Create the LoginContext from the RDBMS callback handler defined in the plugin.xml.

public Object start(final IApplicationContext context) throws Exception
{
String configName = Activator.getConfigurationName();
URL configUrl = Activator.getBundleContext().getBundle().getEntry("jaas_config.txt");
ILoginContext secureContext = LoginContextFactory.createContext(configName, configUrl);
secureContext.registerListener(new ProgressMonitorListener());
Integer result = null;
final Display display = PlatformUI.createDisplay();
try
{
result = (Integer) Subject.doAs(secureContext.getSubject(), getRunAction(display));
}
finally
{
display.dispose();
secureContext.logout();
}
// TBD handle javax.security.auth.login.LoginException

if (result != null && PlatformUI.RETURN_RESTART == result.intValue())
return EXIT_RESTART;
return EXIT_OK;
}


Add the following methods to the Activator class of the plugin.


private static final String CONFIG_PREF = "loginConfiguration";//$NON-NLS-1$

private static final String CONFIG_DEFAULT = "other";

public static BundleContext getBundleContext()
{
return bundleContext;
}

public static String getConfigurationName()
{
return new DefaultScope().getNode(PLUGIN_ID).get(CONFIG_PREF, CONFIG_DEFAULT);
}

Finally the jaas_config.txt file which defines the RDBMS callback Handler name with the Login Module extension ( RDBMSLoginModule ) should be placed in the plugin's root folder.


RDBMS {
org.eclipse.equinox.security.auth.module.ExtensionLoginModule required
extensionId="com.sample.login.RdbmsLoginModule"
debug=true;
};

Wednesday, February 18, 2009

Quick Tips : Opening the Eclipse Preferences with a specfic Preference Page.

When we create PreferencePages in a Eclipse RCP applications, the window->Preferences menu item opens the preferences dialog and all the preferneces are shown in the Preferences window. How about showing a particular preference page from a toolbar button in a view / application.

Before the command framework we can do it using the PreferenceUtil class like belore


PreferencesUtil.createPreferenceDialogOn(shell, preferencePageId, displayedIds, data)


But with the help of the command framework,it is way easy to do this task.


point="org.eclipse.ui.menus">
locationURI="toolbar:com.blog.sample.ui.ButtonView">
commandId="org.eclipse.ui.window.preferences"
label="Preferences"
style="push">
name="preferencePageId"
value="com.blog.sample.ui.CorePreferencePage">






The commandID opens the org.eclipse.ui.window.preferences with all the preferences. The parameter passed to the command tells the Preferences to open the preference window with the page that we want.

Have fun !.