BeanDev: Silent Update/Install mit NBM Dateien
RCP Anwendungen im Unternehmenseinsatz benötigen meistens eine Updatepolitik, die mit dem Standard Autoupdatecenter nicht optimal abgebildet wird. Über das, von der NetBeans Platform gebotene, AutoUpdate Center wird vom Anwendern einiges an Konfiguration und manuellen Eingriffen erwartet, was häufig nicht gewünscht ist.
Die zwei wichtigsten Gründe sind, dass der Anwender zum einem mit der (teilweise fehleranfälligen) Konfiguration der Updates schlicht überfordert ist und zum anderen, dass ein Updaterollout der RCP Anwendung durch Anwender nicht verhindert werden soll.
Bisher wurde empfohlen, dass man stille (silent) Updates über das Kopieren von Dateien in Cluster-Ordner realisieren soll. Der wichtigste Nachteil ist, dass man beim Kopieren exakt wissen muss, in welchem Cluster-Ordner die Installationsdateien kopiert werden sollen.
Der zweite deutlich Nachteil ist, dass man danach den Updater explizit per Befehl aufrufen muss. Das ist für platformunabhängige Anwendungen schon fast ein Totschlagargument gegen diese Vorgehensweise.
Jiri Rechtacek hat in seinem Blog aufgezeigt wie man mit der Autoupdate Services API im Hintergrund Updates aller Bibliotheken durchführt, so dass bestehende (im Plugin-Dialog eingetragene Updatecenter) automatisch komplett aktualisiert werden. Ich habe das bisher so in meinen Anwendungen ähnlich angewendet. Allerdings habe ich teilweise Infrastrukturen, wo ich nicht mal Updatecenter als XML-Dateien auf Servern zur Verfügung stellen kann. Teilweise werden die NBM-Dateien sogar "on-the-fly" erzeugt, um diese direkt zu installieren.
Aber sogar für manuell heruntergeladene NBM-Dateien bietet die Update Autoupdate Services API eine Möglichkeit der Installation. Es ist nämlich nicht zwingend notwendig ein Updatecenter zu haben, um NBM-Dateien zu installieren. Der Plugin Dialog zeigt es ja schon selbst mit der Registerzunge für Downloads.
Zunächst benötigt man eine Modul mit einer ModuleInstall Klasse die am besten über den Assistenten erzeugt wird. Man überschreibt die restored() Methode und sucht sich die passende (bereits) heruntergeladene NBM-Datei. Hier im Beispiel soll das in einer Methode namens getMyDownloadedNBMFile() realisiert werden:
@Override
public void restored() {
// schedule refresh providers
// install update checker when UI is ready (main window shown)
WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
public void run() {
File file = getMyDownloadedNBMFile();
RequestProcessor.getDefault().post(new SilentNBMInstaller(file), 10000);
}
});
}
Der Aufruf der Hilfsklasse SilentNBMInstaller erfolgt erst, wenn die Anwendung einigermaßen hochgefahren ist (invokeWhenUIReady). Der SilentNBMInstaller ist eine schlichte Java Klasse die massiven Gebrauch von der Autoupdate Services API macht. Im Gegensatz zu dem Beispiel von Jiri Rechtacek verwende ich die Progress API gar nicht, um den Fortschritt der Installation zu visualisieren. Auch bekommt der Anwender keine Info die Anwendung neu zu starten. Das kann aber individuell nachgerüstet werden.
public class SilentNBMInstaller implements Runnable {
private final File nbmFile;
public SilentNBMInstaller(File nbmFile) {
this.nbmFile = nbmFile;
}
}
Die run-Methode muss erstmal implementiert werden, außerdem sollte man für das Update (oder die Neuinstallation) aus dem EventDispatchThread heraus sein. Die Methode timeToCheck() liefert true, wenn das Update/die Installation durchgeführt werden soll. Wie das gemacht wird, kann man selbst implementieren. Auch könnte die Prüfung eher im ModulInstall durchgeführt werden.
public void run() {
if (SwingUtilities.isEventDispatchThread()) {
RequestProcessor.getDefault().post(this);
return;
}
if (timeToCheck()) {
runUpdate();
}
}
Das Kernstück ist nun die runUpdate()-Methode. Außerdem unterscheidet sich meine Implementation von Jiri Rechtaceks Beispiel bei dem Ermitteln des Updates. In meinem Fall erzeuge ich einen temporären UpdateUnitProvider zu einer (oder auch mehreren) NBM Datei(en). Dafür nutzt man die Klasse UpdateUnitProviderFactory.
private void runUpdate() {
try {
// Step (1)
boolean isInstalled = false;
// A temporary installer for downloaded nbm files:
UpdateUnitProvider uup = UpdateUnitProviderFactory.getDefault().create("Installer for " + nbmFile.getName(), nbmFile);
Collection<UpdateElement> elements4update = new HashSet<UpdateElement>();
List<UpdateUnit> updateUnits = uup.getUpdateUnits();
for (UpdateUnit unit : updateUnits) {
isInstalled = unit.getInstalled() != null; // We need this flag for different OperationContainer
List<UpdateElement> updates = unit.getAvailableUpdates();
if (!updates.isEmpty()) { // has updates
elements4update.add(updates.get(0)); // add plugin with highest version
}
}
// ... continue with (2)
Wie man oben sieht, wird aus dem UpdateUnitProvider die Collection der UpdateUnits gezogen. Außerdem merke ich mir, ob die NBM-Datei neu installiert werden soll, oder ein Update erfährt. Das ist notwendig, weil da für unterschiedliche OperationContainer erzeugt werden müssen. Das habe ich in eine extra Methode ausgelagert:
// Step (2)
if (elements4update.size() > 0) {
OperationContainer<InstallSupport> operation = getOperationContainer(elements4update, !isInstalled);
// ... continue with (3)
Die Methode getOperationContainer liefert mir den passenden Container, der die folgende Installation oder das Update durchführen soll.
private OperationContainer<InstallSupport> getOperationContainer (Collection<UpdateElement> elements4update, boolean install) {
OperationContainer<InstallSupport> container = install ? OperationContainer.createForInstall() : OperationContainer.createForUpdate ();
for (UpdateElement element : elements4update) {
if (container.canBeAdded (element.getUpdateUnit(), element)) {
OperationInfo<InstallSupport> operationInfo = container.add (element);
if (operationInfo == null) {
continue;
}
container.add (operationInfo.getRequiredElements ());
}
}
return container;
}
Im dritten Schritt (der runUpdate-Methode) kann ich den OperationContainer (vom Typ InstallSupport) nun endlich für die Installation oder das Update nutzen:
// Step (3)
InstallSupport install = operation.getSupport();
Validator validator = install.doDownload(null, false); // Yes we need this call (but the file is already downloaded)
Installer installer = install.doValidate(validator, null); // Validating
Restarter restarter = install.doInstall(installer, null); // Installing
// ... continue with (4)
Auch wenn man das NBM-File schon lokal auf der Festplatte haben wird, müssen zwingend die drei Aufrufe doDownload/doValidate/doInstall durchgeführt werden. Nur so bekommt man die Objekte Validator/Installer/Restarter, um die Kette auszuführen. Die null-Parameter sind die von mir vernachlässigten ProgressHandle Objekte für die Darstellung des Verlaufs. Die Installation ist aber meistens so schnell, dass das UI in der Statuszeile gar nicht reagiert.
Der Installationsvorgang ist mit den obigen Schritten "vorbereitet". Wir müssen nur dem InstallSupport sagen, wann der Abschluss erfolgen soll.
// Step (4)
// It's a silent updated library. For the current run it's unnessesary to ask the user for a restart.
install.doRestartLater(restarter);
// ... continue with (5)
Für den Abschluss der Methode runUpdate() noch dieses hier:
// Step (5)
}
} catch (OperationException ex) {
Exceptions.printStackTrace(ex);
}
}
Damit wäre die Klasse SilentNBMInstaller vollständig. Jiri Rechtacek empfiehlt noch, die Lizenzen der Updates abzufragen und (ggf.) dem Anwender vorzulegen. Das ist spannend bei 3rd-party Bibliotheken und Plugins. Für Silent Updates von Unternehmensanwendungen ist das eher hinderlich und wird bei mir auch damit nicht abgefragt.
In Jiris Blog findet man dazu mehr, wie man die Lizenzen ausliest.
SilentNBMInstaller installiert auch nur einzelne NMB-Dateien. Das ist aber keine notwendige Einschränkung, weil der UpdateUnitProviderFactory.create Methode mehr als eine Datei für die Installation übergeben werden darf. Allerdings erhöht sich dann der Komplexitätsgrad, wenn einige NBM Dateien neu installiert werden und andere nur ein Update benötigen (verschiedene OperationContainer!). Für das einfache Beispiel habe ich es damit erstmal bei einer Unterscheidung einer NBM-Datei belassen.
Beste Grüße,
Josch.
Da werden Sie geholfen:
Das deutsche NetBeans Forum
![Validate my RSS feed [Valid RSS]](http://www.sepix.de/fileadmin/valid-rss.png)