Skip to content

If you had the chance to delight a customer a zero cost, would you? Not if you're #AirFrance

BusinessTravel For a long time, Air France in general and the Airport Paris (CDG) in particular have been on my avoid-at-all-cost airlines/airports.  But sometimes, it's not avoidable, for instance for getting to Toulouse for EclipseCon France.

Every time I have bad experience with a service/product/whatever and I try it again, I really try to open my mind.  Maybe they will delight me this time, inspite of my bad experience in the past.  Turns out I got disappointed again.  This time in a scenario that would have been easy for Air France to fix, and for free on top.

See, I like sitting in an aile seat on planes.  I had one.  It was broken.  It didn't arrest its position, which was annoying.  So I asked, before take-off, whether there was a way for fixing it, or to reseat me to a functioning aile seat.  The Stuadress was nice but informed me that none was availble.  When I pointed to the ten free aile seats further up front, she informed me that she could not reseat me to business class.  Mind you, this was a one-hour flight from Düsseldorf to Paris.  So "business class" was just a tid bit more leg room, nothing else.  No fancy dinner or anything like that.

Being an avid reader of Seth Godin, he would cringe.  Was I unhappy? Yes.  Was there a way for fixing it? Yes.  Would it have cost the airline anything? Not a cent.  Why does Air France not empower their staff to delight, if it does not cost a thing?  I have no idea.  Just to clarify, I did not feel entitled to be reseated.  It's the airline's decision.  I just think that it's a bad decision that will make me try to avoid Air France even harder (and help spread the word).

In the Stuardesses defence, I saw her talk to her supervisor, after our conversation.  The fact that I didn't get reseated implies that heir supervisor either was not empowered, or did not care.  One as bad as the other.  Air France, good luck with your ongoing race to the bottom.

"Honest" open standards should come with an open source reference implementation, like #RMF for #ReqIF

BusinessRequirements

In IT Standards and Open Source (Repeat), Dirk Riehle argues that there are two types of standards, "honest" and "dishonest".  He cautions to "be wary of any standard that does not come with an open source reference implementation; it might not be an honest standard." The ReqIF standard comes with a reference open source implementation, Eclipse RMF. Many other activities indicate that there is an honest desire to be open.

Support the #TrueCrypt fork (and download the uncrippled 7.1a)

LifestyleTechnology

TrueCrypt is shutting down. I strongly doubt that the official reason (loss of interest) is the true one.  Why release a crippled 7.2 (cannot create new encrypted containers)?  Why remove all references to older versions and to the source code (the source code is not accessible on SourceForge any more)?

May it as it be, I am glad to see that someone already picked up this amazing open source project. So please download the uncrippled 7.1a from the new truecrypt.ch in Switzerland.  If the project picks up speed and delivers, I'll certainly make a donation.

Writing Eclipse Wizards

Eclipse RCP

I've been creating a lot of Eclipse Wizards lately, and it's quite a challenge to keep the code clean, understandable and testable.  Eventually, I came up with a pattern that seems to work reasonably well.  I document it here for my and everybody else's benefit.

Here are the key ideas:

  • Eclipse Facade:I am not a big fan of Eclipse Plug-In tests (vs. regular unit tests).   Therefore, everything that's Eclipse-related should be as simple as possible, which we do by building a Facade.
  • Data Binding: We use data binding in the Wizard pages to keep them as simple as possible
  • Per-page Settings: Each page has a corresponding Settings object that manages the page's data.  Specifically, it also validates the values for that page. There are no Eclipse- or GUI-dependencies in the Settings, so they can easily be tested.
  • Central settings store: The per-page settings are managed in a central settings store that contains all data  This allows us to persist the wizard settings (if desired), and also contains the finish() method that completes the execution of the wizard. This store has no GUI or Eclipse-dependencies either.

Here is a class diagram, for one page, but of course there could be multiple:

Here is a brief description of each element:

Wizard

The Wizard instantiates the Facade and the Store (which takes the Facade as an argument), and then creates the pages (which take the store as an argument).  It also calls performFinish() on the Store, which does all the hard work.

public class MyWizard extends Wizard implements IImportWizard {

	private ExchangeStore store;

	@Override
	public void init(IWorkbench workbench, IStructuredSelection selection) {
		IEclipseFacade facade = new EclipseFacade(selection);
		store = new ExchangeStore(facade);

		addPage(new SelectProjectPage(store));
		addPage(new ImporterPage2SelectFile(store));
		addPage(new ImporterPage3SelectAttributes(store));
	}

	@Override
	public boolean performFinish() {
		return store.performFinish();
	}
}

AbstractSettings

The AbstractSettings provide the Bean functionality, as described by Lars Vogel. It manages the registration of PropertyListeners and provides firePropertyChange() to announce changes.

Pages

I use an abstract base class for setting up the way I like to build GUIs.  But important is updateStatus(), which propagates the validation result.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	protected void updateStatus(IStatus status) {
		setErrorMessage(null);  // Clear previous error messages - also braindead.
		setMessage(status.getMessage(), status.getSeverity());
		if (status.getSeverity() == Status.OK) {  // Braindead: The message is not empty on ok!
			setMessage(null, Status.OK);
			setPageComplete(true);
		} else if (status.getSeverity() == Status.INFO) {
			setPageComplete(true);
		} else {
			setPageComplete(false);
		}
	}

The pages themselves build the GUI and bind it to the Settings.  Key here is the DataBindingContext:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
		DataBindingContext ctx = new DataBindingContext();

		final Combo combo = new Combo(form.getBody(), SWT.READ_ONLY);
		combo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		ctx.bindList(WidgetProperties.items().observe(combo), BeanProperties
				.list("projectNames").observe(settings));

		ctx.bindValue(WidgetProperties.text().observe(combo), BeanProperties
				.value("projectName").observe(settings));

Note that we bind a List and a Value: The first is populating the combo with values, the second reacts to selections.

PageSettings

The PageSettings need getters for all the bound Widgets, and optionally setters, if they can change. Setters must fire, if they change a value.  Many values are just propagated from the Store, which may have an impact on the PageSettings.  If they have an impact, the PageSettings must register a listener on the Store to react to changes.  For example, Page 1 may allow the user to select a project, which would be set on the Store, which in turn would fire an event.  Page 2 may show the list of files.  Thus, Page 2 would register a listener on the Store that reacts to changes in the Project. Upon a change the list of files would be updated, which triggers firing of the corresponding property.  As an example, here the Settings for selecting a Project:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SelectProjectSettings extends ExchangeSettings {

	private ExchangeStore store;

	public SelectProjectSettings(ExchangeStore store) {
		if (store == null) throw new NullPointerException();
		this.store = store;
	}
	
	public List<String> getProjectNames() {
		return store.getProjectNames();
	}

	public String getProjectName() {
		return store.getProjectName();
	}

	public void setProjectName(String projectName) {
		String oldProjectName = store.getProjectName();
		store.setProjectName(projectName);
		firePropertyChange("projectName", oldProjectName, getProjectName());
	}

	public IStatus validatePage() {
		if (getProjectNames().contains(getProjectName())) {
			return ValidationStatus.ok();
		} else {
			return ValidationStatus.warning("Please select a Project");
		}
	}
}

 ExchangeStore

 The ExchangeStore is obviously very project specific.  But I want to demonstrate how it delegate Eclipse-specific information.  For instance, it contains the names of all Projects in the Workspace, which are used to populate the combo box in the wizard.  As this is Eclipse-specific, it is delegated to the Facade:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ExchangeStore extends ExchangeSettings {

	private final IEclipseFacade facade;

	public ExchangeStore(IEclipseFacade facade) {
		this.facade = facade;
	}
	
	public List<String> getProjectNames() {
		return facade.getProjectNames();
	}

        ... much more stuff ...
}

Facade

And last, the facade abstracts Eclipse-specific, project-specific functionality. To pick up the example again, this is how we get the project names in the actual implementation:

1
2
3
4
5
6
7
8
9
	@Override
	public List<String> getProjectNames() {
		List<String> list = new ArrayList<String>();
		IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
		for (IProject project : projects) {
			list.add(project.getName());
		}
		return list;
	}


tweetbackcheck