Eclipse Extension Points und Extensions

Eingetragen bei: Eclipse, RCP | 1

Eclipse beinhaltet ein hoch modulares Konzept um Erweiterungen Plugin-übergreifend realisieren zu können. Das zu erweiternde Plugin bietet einen Extension-Point an, an dem andere Plugins mit ihren Extensions andocken können. Beide Arten an Plugins müssen hierbei zwangsläufig im Manifest als singleton definiert sein.

Um einen Extension-Point zu definieren, wird die plugin.xml Datei des zu erweiternden Bundles geöffnet und zum Reiter Extension Points gewechselt. Sofern keine plugin.xml Datei existiert, kann alternativ im Manifest (MANIFEST.MF) der genannte Reiter aktiviert werden, Eclipse erstellt nun automatisch die plugin.xml Datei. Nun kann ein Extension-Point erzeugt werden, als ID und Namen erhält er im folgenden Beispiel den Wert de.mz.myExtensionPoint und myExtensionPoint.


Nach erfolgreicher Erstellung des Extension-Points wird automatisch das dazugehörige Schema geöffnet. Auch wenn es nicht zwingend notwendig ist, sollten alle Attribute im Schema – Reiter Definition – definiert werden. Nur so können die Felder beim Anlegen einer Erweiterung bequem im Plug-In Manifest Editor über die UI angegeben werden können. Will man später Veränderungen am Schema vornehmen, so ist dieses in der Dateistruktur des Projektes unter schema/myExtensionPoint.exsd zu finden.

Hier wird nun ein neues Element mit dem Namen client erzeugt, dem zwei Attribute zugewiesen werden. Das Erste Attribut hat den Namen class und ist vom Typ Java. Hier hat man nun die Möglichkeit ein Interface zu hinterlegen, welches von der im Attribut class verwiesenen Klasse zu implementieren ist. Im folgenden Beispiel ist das zu implementierende Interface IMyExtension.

Das Interface selber enthält für Beispielzwecke die Methode doSomething:

public interface IMyExtension {
public void doSomething(String text);
}

Das zweite Attribut erhält den Namen text und ist vom Typ String. Hier sind keine weiteren Einstellungen nötig.

Per Context-Menü wird der extension zunächst ein Choice-Element zugewiesen, welches wiederum den Eintrag client erhält. Damit die Extension beliebig viele Client-Einträge erhalten kann, wird die Max Occurrences des Client-Elements auf Unbounded gesetzt. Eine Extension kann also mehrere Erweiterungen enthalten.

Die Plugin.xml hat nun folgenden Eintrag erhalten:

<extension-point id=“de.mz.myExtensionPoint“ name=“myExtensionPoint“ schema=“schema/myExtensionPoint.exsd“/>

Damit andere Bundles nun tatsächlich den Extension-Point erweitern können, müssen sie das Interface IMyExtension implementieren können. Daher muss im Manifest das Package des Interfaces exportiert werden:

Export-Package: de.mz

Damit ist die Definition des Extension-Points abgeschlossen und wir können uns der Erstellung einer Extensions zuwenden. Öffnen wir hierzu die plugin.xml Datei des Bundles, welches den Extension-Point erweitern soll. Auf dem Reiter Extensions kann zwischen den möglichen Erweiterungen gewählt werden, auch der zuvor erstelle Extension-Point de.mz.myExtensionPoint wird hier aufgeführt. Per Context-Menü können hier die Client-Erweiterungen hinzugefügt werden. Für die im Schema deklarierten Attribute stehen im Plug-In Manifest Editor Eingabehilfen zur Verfügung, so kann bequem für das class Attribut eine Implementierung und für das text Attribut ein String angegeben werden.

Diese Einstellung führt zum folgenden Eintrag in der plugin.xml Datei:

<extension point=“de.mz.myExtensionPoint“>
<client
class=“de.mz.MyExtensionImpl“
text=“Hallo Welt“>
</client>
</extension>

Extension-Point und eine dazu passende Extension wurden bereits erstellt, nur Verwendung findet die Erweiterungen bislang noch nicht. Doch hierzu bietet Eclipse die ExtensionRegistry an, auf die man über die Runtime-Platform Zugriff erhält:

IConfigurationElement[] extensions = Platform.getExtensionRegistry().getConfigurationElementsFor(„de.mz.extension.example.myExtensionPoint“);
for (IConfigurationElement extension : extensions) {
try {
String text = extension.getAttribute(„text“);
IMyExtension myExtension = (IMyExtension) extension.createExecutableExtension(„class“);
myExtension.doSomething(text);
// do with myExtension what you have to do
} catch (CoreException e) {
// do some logging
}
}

In obigem Beispiel erhält man alle Erweiterungen die zum Extension-Point de.mz.extension.example.myExtensionPoint gehören. Nun können die Attribute der einzelnen Erweiterungen erfragt werden – hierbei ist es nicht relevant, ob diese im Schema definiert sind – und für das Klassen-Attribut ein entsprechendes Objekt erstellen werden (createExecutableExtension). Bei einem optionalen Klassen-Feld macht es Sinn, sich zunächst zu vergewissern, dass das Attribut einen Wert zugewiesen bekommen hat. Andernfalls wird nämlich eine CoreException geworfen, die über diese Abfrage verhindert werden kann.

Auch wenn das Beispiel auf die Eclipse 3.x Plattform ausgelegt ist, funktioniert der Mechanismus grundsätzlich auch bei Eclipse 4. Hier spart man sich den Umweg über die Runtime-Platform, da sich die ExtensionRegistry bequem per Dependency Injection injizieren lässt.

Will man das Beispiel testen, kann das Laden der Extensions in einem Activator vorgenommen werden, der hierfür lediglich im Manifest eingetragen werden muss:

Bundle-Activator: de.mz.Activator

Nun kann das Extension-Point Bundle als OSGI Framework ausgeführt werden. Treten hierbei Fehler auf, sollten in der Run-Configuration zunächst alle Bundles deselektiert und anschließend nur die Plugins ausgewählt werden, die den Extension-Point und die Extensions beinhalten. Natürlich werden auch Eclipse spezifische Bundles benötigt, die sich über einen Klick auf Add Required Bundles hinzufügen lassen.

Verwendung einer SQLite Datenbank in einer Android App

Eingetragen bei: Android, Java | 0

Will man in einer Android Anwendung Daten speichern, kann hierzu bequem die in das Android SDK integrierte SQLite Datenbank verwendet werden. Diese ist besonders für den mobilen Anwendungsbereich konzipiert, da sie mit gerade mal 250 KByte extrem genügsam im Bezug auf Speicherplatz ist.

Im folgenden Beispiel sollen die Einstellungen einer Anwendung in der Datenbank gespeichert werden. Hierzu wird eine SettingsActivity erstellt – nicht vergessen, die Activity im Android Manifest zu definieren –  wobei das Layout in einer XML-Datei definiert ist. Als variables Feld erhält die Activity einen Benutzer-Namen, welcher vom Benutzer editiert werden kann. Außerdem gibt es den obligatorischen Speichern-Button, damit die Änderungen vom Benutzer auch gespeichert werden können:

<EditText
android:id=“@+id/name“
android:layout_width=“fill_parent“
android:layout_height=“wrap_content“  android:background=“@android:drawable/editbox_background“
android:text=““
android:padding=“3dip“ />

<Button
android:layout_column=“1″
android:id=“@+id/save“
android:layout_width=“wrap_content“
android:layout_height=“wrap_content“
android:layout_alignParentRight=“true“
android:layout_marginLeft=“10dip“
android:text=“Speichern“ />

Um in den Activities der Anwendung direkten Zugriff auf die UI-Komponenten zu haben, wird das Dependency Injection Framework roboguice verwendet, welches zur Zeit in Version 1.2.2 unter folgendem Link zum Download bereit liegt: http://code.google.com/p/roboguice/wiki/Downloads

Zusätzlich zu roboguice wird noch die Google Library guice benötigt, die man unter folgendem Link erhält: http://code.google.com/p/google-guice/downloads/list

Hierbei ist zu beachten, dass nicht die aktuelle Version 3.0, sondern guice-2.0-no_aop.jar benötigt wird. Beide Libraries werden dem Classpath hinzugefügt. Nun wird im Android Manifest unter Application der Name roboguice.application.RoboApplication gesetzt. Dies ist nötig, da die Activities nun nicht mehr von Activity oder ListActivity direkt erben, sondern von RoboActivity bzw. RoboListActivity.

In der folgenden SettingsActivity wird über die Annotation @InjectView per Dependency Injection auf die im XML definierten UI-Elemente per Id zugegriffen:

public class SettingActivity extends RoboActivity {

private DatabaseManager databaseManager;
@InjectView(R.id.name)
private EditText nameText;
@InjectView(R.id.save)
private Button saveButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.setting);
databaseManager = new DatabaseManager();
final Settings settings = databaseManager.fetchSetting();
nameText.setText(settings.getName());
saveButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
settings.setName(nameText.getText().toString());
if (settings.getId() == null) {
databaseManager.createSettings(settings);
} else {
databaseManager.updateSettings(settings);
}
}
});
}

@Override
protected void onDestroy() {
super.onDestroy();
databaseManager.close();
}
}

In der onCreate-Methode wird ein DatabaseManager erzeugt, der sich um die Datenbank Connection kümmert, aber hierzu später noch mehr. Ebenfalls über den DatabaseManager werden die Settings bezogen, die in der UI dargestellt werden. Hierzu wird dem nameText, der Name aus den Settings zugewiesen. Nun wird noch dem Save-Button ein onClickListener hinzugefügt, der die Änderungen der UI dem Settings Objekt übergibt und dieses anschließend in der Datenbank speichert.

Wichtig ist, dass in der onDestroy Methode der Activity, die Datenbank-Connection wieder geschlossen wird – verlässt der Benutzer also die Einstellungen wird automatisch die Datenbank Verbindung zurück gesetzt.

Nun zur eigentlichen Interaktion mit der Datenbank über den DatabaseManager:

public class DatabaseManager {

private static final String DB_FILE = „/data/data/myapp.db“;
private SQLiteDatabase database;

public DatabaseManager() {
File file = new File(DB_FILE);
if (!file.exists()) {
database = SQLiteDatabase.openOrCreateDatabase(file, null);
database.execSQL(„create table Settings (id integer primary key autoincrement not null, name text);“);
} else {
database = SQLiteDatabase.openOrCreateDatabase(file, null);
}
}

public void close() {
database.close();
}

public Settings createSettings(Settings settings) {
ContentValues initialValues = createContentValues(settings);
long id = database.insert(„Settings“, null, initialValues);
settings.setId((int) id);
return settings;
}

public long updateSettings(Settings settings) {
ContentValues updateValues = createContentValues(settings);
return database.update(„Settings“, updateValues, „id=“ + settings.getId(), null);
}

public Settings fetchSetting() {
try {
Cursor cursor = database.query(„Settings“,
new String[] {
„id“, „name“
}, null, null, null, null, null);
cursor.moveToFirst();
if (!cursor.isAfterLast()) {
Integer id = cursor.getInt(cursor.getColumnIndexOrThrow(„id“));
String name = cursor.getString(cursor.getColumnIndexOrThrow(„name“);
Settings settings = new Settings(id, name);
return settings;
} else {
return new Settings();
}
} catch (SQLiteException e) {
return new Settings();
}
}

private ContentValues createContentValues(Settings settings) {
ContentValues values = new ContentValues();
values.put(„name“, settings.getName());
return values;
}
}

Im Konstruktor, der in der onCreate-Methode der SettingsActivity aufgerufen wurde, wird zunächst überprüft, ob die Datenbank-Datei bereits angelegt wurde. Ist dies nicht der Fall, werden die Settings der Anwendung Initial gespeichert und zusätzlich zum Öffnen der Datenbank muss die Settings Tabelle mittels SQL-Statement generiert werden.

Der nächste Aufruf der SettingsActivity lädt die bereits gespeicherten Settings über die fetchSettings-Methode. Hier wird auf der Datenbank eine Query gefeuert, der die gewünschten Properties mitgegeben werden und die sonst keinerlei Einschränkung enthält, es werden also wirklich alle Settings geladen. Das Ergebnis ist über einen Cursor zugreifbar, der zunächst über die moveToFirst-Methode auf die erste Position gesetzt werden muss. Die über den Cursor zugreifbaren Properties werden in einem Settings Objekt gespeichert, bzw. falls die Datenbank noch keinen Eintrag enthält wird ein neues Settings Objekt erzeugt und der SettingsActivity geliefert.

Werden die Einstellungen vom Benutzer geändert und der Speicher-Button betätigt, wird das Settings-Objekt entweder Initial in der Datenbank gespeichert oder aktualisiert. Dies hängt davon ab, ob die Settings bereits eine eindeutige Id enthalten. Bei dem Update-Statement wird eine Where Clause benötigt, die eine Einschränkung auf die gegebene Id – also den Primär Schlüssel – enthält. Andernfalls wird das Objekt neu erzeugt und die von der Datenbank vergebene Id in dem Settings-Objekt gespeichert.

Beim Insert bzw. Update wird nicht das Settings-Objekt selber verwendet sondern das generische Objekt ContentValues, dem per put-Befehl die einzelnen Properties zugewiesen werden können.

Sobald man nun noch die Permissions im Android-Manifest richtig gesetzt hat, steht dem ersten Probelauf jetzt nichts mehr im Wege.

<uses-permission android:name= „android.permission.WRITE_EXTERNAL_STORAGE“/>
<permission-group android:name=“android.permission-group.STORAGE“/>

Insgesamt lässt sich sagen, dass der Zugriff auf die SQLite Datenbank bei Android Anwendungen recht komfortable gestaltet ist, gerade weil hier alle benötigten Libraries bereits gegeben sind. Nur Schade, dass man nicht direkt mit Entities arbeiten kann, sondern dass diese auf die Datenbank Inhalte gemappt werden müssen. Es wäre angenehmer auf die Cursor zu verzichten und besser direkt über die Entity an die gewünschten Properties zu gelangen – ebenso wie beim Mappen der Properties auf die ContentValues ein zusätzlicher Schritt benötigt wird.

Veröffentlichung einer Fremdbibliothek als OSGI Plugin

Eingetragen bei: Eclipse, Java, RCP | 0

Wer mit OSGI arbeitet kennt das. Man findet eine Library die genau das tut, was man benötigt, es liegt nur keine OSGI-Plugin-Variante zum Download bereit.

Eine Möglichkeit ist nun das Jar-Archive zu downloaden und in Eclipse ein neues Projekt Plug-in from Existing JAR Archive zu erstellen. Im nächsten Schritt können nun External JAR Archives hinzugefügt werden. Benötigt man verschiedene Libraries, bzw. hat die gewünschte Library weitere Dependencies, können diese hier direkt hinzugefügt werden. Selbst wenn die verschiedenen Archive eine gleiche package-Struktur aufweisen, ergibt dies keine Probleme, die class-Dateien werden in die entsprechenden Packages gemergt.

Theoretisch ist man nun fertig und man kann die gewünschte Library verwenden. Allerdings werden sich dann die Kollegen bedanken, da sie nun ein weiteres Projekt auschecken müssen. Also exportiert man das soeben erstellte Projekt noch als Deployable plug-ins and fragments, legt das erstellte OSGI-Plugin in die Target-Plattform und kann nun das Projekt wieder schließen.

Sofern die Library nicht in einem Maven-Kontext benötigt wird, ist man nun tatsächlich fertig, andernfalls muss man sie noch manuell in ein Maven-Repository legen.

Eine zweite Möglichkeit, um aus einer Fremdbibliothek ein OSGI-Plugin zu erstellen, ist die Verwendung des Maven Plugins Felix von Apache. Hier wird eine pom.xml der folgenden Art benötigt:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>de.example</groupId>
<artifactId>de.example.project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>de.example.project</name>
<packaging>bundle</packaging>

<dependencies>
<dependency>
<groupId>de.example</groupId>
<artifactId>de.example.dependency</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/xxx.jar</systemPath>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Export-Package>*</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

Wichtig ist hierbei, dass packaging den Wert bundle erhält, wofür allerdings das Plugin org.apache.felix korrekt eingebunden seien muss. Zu beachten ist hier der Wert true für Extensions.

Als dependency wird im obigen Beispiel die Library de.example.dependency benötigt, die sich in dem aktuellen Maven-Projekt in dem Jar-Archive xxx.jar befindet. Der Ort des Jar-Archives wird im systemPath definiert.

Die class-Dateien der auf diese Weise angegebenen Dependencies werden beim Bauen des Maven-Projektes mittels package mit ins target gelegt. Weiterhin erstellt bei obiger Definition das Plugin Felix ein Manifest in dem alle packages der eingebundenen Libriries exportiert werden. Möchte man diese Einschränken, so können die zu exportierenden packages explizit in der pom-Datei unter Export-Package definiert werden.

Als Fazit lässt sich sagen, dass beide Varianten Arbeit bedeuten und man stehts hoffen sollte, dass die benötigten Libraries bereits als OSGI-Plugin zur Verfügung stehen. So lohnt sich immer zuerst ein Blick auf das SpringSource Enterprise Bundle Repository, da hier bereits viele Libraries als OSGI-Bundle vorliegen: http://ebr.springsource.com/repository/app/