Donnerstag, 26. August 2010

Volltextindex in Oracle 10g2 auf Win Server 2003 64 Bit

Für die Erstellung von Volltextindizes aus dem Inhalt (fast) beliebiger Dokumente wird der AUTO_FILTER benutzt. Dieser greift auf das Programm CTXHX.EXE zu. CTXHX.EXE liegt bei der 64 Bit Installation unter %ORACLE_HOME%\BIN\ctxhx (Im Gegensatz zu den 32 Bit Installationen).

Nach der Standardinstallation funtioniert der AUTO_FILTER allerdings nicht, er bricht immer mit dem Fehlercode 1 ab.

Die Lösung ist dass AUTO_FILTER wohl in der Registry unter
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ORACLE\KEY_OraDb10g_home1
den Oracle-Registryeintrag erwartet, dieser wird durch den Installer aber nicht angelegt.

Durch Kopieren des Schlüssels
HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_OraDb10g_home1
nach
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ORACLE\KEY_OraDb10g_home1
mit allen Untereinträgen kann dieses Problem gelöst werden.

Exportieren Sie aus REGEDIT den Schlüssel
HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_OraDb10g_home1
, öffnen die Exportdatei in Notepad und fügen das Wow6432Node an der entsprechenden Stelle ein:
[HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_OraDb10g_home1] 
wird zu
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ORACLE\KEY_OraDb10g_home1]

Danach importieren Sie die Datei, und fertig.

Das Problem ist auf unserem Server aufgetreten, auf dem QMA (Qualitätsmanagement-Software) als Hosting-Service bzw. "Software as a Service" betrieben wird.

Montag, 23. August 2010

Replikation für QMA

Im Rahmen eines Projekts mussten wir uns Gedanken über die Replikation von Daten aus der QMA (Qualitätsmanagement, Dokumentenlenkung) - Anwendung mit dem QMA-Webportal machen. Die Datenbank ist ein Oracle 10g SQL Server. Der bisherige Ansatz per Export und Import die Datenbank abzugleichen hat in diesem Fall einige Nachteile, denn es ist damit relativ schwierig lediglich die Aktualisierungen zu übertragen. Der Plan war, alle Änderungen in einer Log-Tabelle aufzuzeichnen und entweder manuell oder zeitgesteuert einen Export der Änderungen durchzuführen. Dieser Export soll per Filetransfer auf den Server übertragen und dort automatisch importiert werden.

Grundlage für den Export ist nun die Oracle Datapump-API (DBMS_DATAPUMP), die per Stored Procedures gesteuert wird. Anhand der Log-Tabelle, die durch Insert/Udate/Delete - Trigger gepflegt wird, wird lokal ein "Spiegel" der Änderungen angelegt. Diese Spiegeltabellen werden per Datapump - API exportiert. Die Dateien werden per Filetransfer in ein überwachtes Verzeichnis übertragen. Bei Ankunft neuer Dateien in diesem Verzeichnis wird der Prozess dann quasi rückwärts durchgeführt.

Zur Zeit gibt es noch einige Anforderungen die mit diesem Ansatz nicht optimal gelöst werden: Löschungen werden noch nicht berücksichtigt, und es findet noch keine Konflikterkennung bei Updates statt. Der Vorteil der Datapump-API ist dass die Datendateien alle Datentypen aufnehmen und transportieren können, so auch die BLOB-Felder. Ein besserer Ansatz wäre wenn man Journale schreiben könnte die alle Aktivitäten auf der Quelle im Ziel einfach nachfahren. Das ist wahrscheinlich der Ansatz bei der Advanced Replikation, diese API ist jedoch um einiges komplizierter.

Wir werden an dieser Stelle weiter berichten wie sich die "einfache" Replikation bewährt und entwickelt, und demnächst nach der Erprobungsphase die Quellen veröffentlichen.

Dienstag, 17. August 2010

Zweischrimbetrieb für DMS

Wir bereiten gerade das DMS für den Zweischirmbetrieb vor. Dabei können die Dokumentansichten auf einem zweiten Monitor dargestellt werden, die Suche und die Metadaten verbleiben auf dem primären Schrim.

Mit Qt geht das recht einfach, da das QDesktopWidget bequemen Zugriff auf die vorhandenen Screens und die verfügbare Geometrie erlaubt.

Dabei sollte man aufpassen nicht die Geometrie des QDesktopWidgets zu benutzen, sondern die Geometrie des Schirms (Screen) auf dem man ein Widget darstellen möchte. Je nach Einstellung der Grafikkarte erstreckt sich der Desktop über beide Monitore, und ein zentrieren eines Widgets im Desktop sieht merkwürdig aus, da die eine Hälfte auf dem primären Schrim landet und die andere Hälfte auf dem sekundären Schirm.

Bei der Positionierung eines Widgets auf den sekundären Schirm gilt weiter zu beachten, dass dieser Schirm (wenn der Desktop sich über beide Schirme erstreckt) einen linken Offset hat und man das zu erstellende Widget entsprechend dorthin verschieben muss.

Montag, 16. August 2010

Dateisystemzugriff für Dokumente in Datenbanken

Ein Problem beim Dokumentenmanagement ist immer die Entscheidung ob die Dokumente in der Datenbank oder im Dateisystem gespeichert werden. Es gibt bei dieser Frage wie immer einige Pros und Cons.

Sowohl in unserem allgemeinen DMS als auch im CRM-Modul, der Dokumentenlenkung und im Laborprojektsystem speichern wir die Dokumente als Blobs innerhalb der Datenbank.

Der Grund dafür ist dass die Kontrolle über die Dokumente möglichst bei der Applikation bleiben soll, um die applikationsbasierten Zugriffsberechtigungen beizubehalten, ein Checkout/Checkin mit Versionierung auf der Datenbank zu führen und den Transport der Dokumente (Backup, Migration etc.) möglichst über Export- und Importfunktionen durchführen zu können. Weiterhin unterliegen die Dokumentdaten damit auch der Transaktionskontrolle der Datenbank. Wir können somit auch ohne zusätzliche Administration von Verzeichnissen verhindern dass Dokumente durch Löschen, Drag&Drop oder ähnlichem aus der Kontrolle der Applikation verschwinden, und Änderungen können sehr genau verfolgt werden.

Nachteilig ist dass der Checkout/Checkin-Mechanismus für den Anwender oft nicht transparent ist, denn er muss neben dem Speichern des Dokuments in der Applikation (Word, Excel, etc.) noch einmal explizit einen Checkin-Vorgang durchführen, oder die Applikation muss die Dateien ständig überwachen ob sie verändert wurden und automatisch übernommen werden können.

Wünschenswert wäre wenn sich die Dokumente wie über das normale Dateisystem ansprechen lassen und das Speichern des Dokuments (fast) direkt zum Update der Datenbank führt.

Wir haben uns dazu entschieden diesen Mechanismus mittels der Bibliothek DOKAN zu realisieren.

Dokan bietet die Möglichkeit ein Filesystem ohne das Windows-Driver-Development-Kit im User-Space zur Verfügung zu stellen. Wir können unsere Dokumente über die Dokan-Biblitohek in einem virtuellen Dateisystem einbinden und über ein Laufwerk zur Verfügung stellen, mittles Windows-Share auch über einen UNC-Serverpfad.

Damit können wir Dokumente aus der Datenbank einfach per ShellExecute auf dem Share öffnen, und nach dem Schliessen durch einen Hintergrundprozess automatisch übernehmen lassen.

Diese Option haben wir in der Laborprojektverwaltung realisiert, die Freigabe ist für diese Woche oder in der nächsten Woche geplant.

Der Vorteil für den Anwender liegt darin dass er nur noch dafür sorgen muss die Datei in der Anwendung zu schliessen und vorher seine Änderungen zu speichern, ein explizites Checkin ist nicht mehr nötig.

Acrobat PDF Viewer einbetten

Für die Anzeige von PDF-Dateien innerhalb unseres KiS verwenden wir den Acrobat Reader als ActiveX Modul. Ein (anscheinend schon lange bekannter) Fehler bis zur Acrobat Reader Version 9.3.3 bringt bei betätigen der Tab-Taste innerhalb des Acrobat Reader Fensters das Programm zum Absturz.

Die einzige Möglichkeit den Fehler zu verhindern ist die TAB-Taste innerhalb der Windows-Nachrichtenschleife des Programms abzufangen. Unsere Lösung für die Qt-Klassenbibliothek:
//Erstellen des Controlsviewer()->setControl("AcroPDF.PDF");
WId wid = viewer()->winId();
if (m_AdobeWindows.isEmpty())
{  //Installieren des Event-Filters im eventDispatcher
prevFilter = QAbstractEventDispatcher::instance()->setEventFilter( ::eventFilter );
}
//Eintragen des Windows-Handle in die Liste der zu überwachenden Fenster
m_AdobeWindows.append(wid);

Beim Beenden des Controls wird der Viewer aus der Liste ausgetragen:
if (!viewer()->isNull())
{
m_AdobeWindows.removeAll( viewer()->winId() );
//Wenn keine Controls mehr überwacht werden wird der EventFilter wieder zurückgesetzt.
if (m_AdobeWindows.isEmpty())
{
QAbstractEventDispatcher::instance()->setEventFilter( prevFilter );
}
}

Der Event-Dispatcher sieht folgendermassen aus:
//Liste der zu überwachenden Fenster
QList m_AdobeWindows;
//Nimmt einen ggf. bereits gesetzten EventFilter auf
static QAbstractEventDispatcher::EventFilter prevFilter = 0;

static bool eventFilter(void *message)
{  LPMSG msg = (LPMSG)message;
//Diese Message ist der Auslöser für die Schutzverletzung
if (msg->message == 0x1450)
{
//Hier prüfen wir ob der Adressat der Nachricht ein Kindfenster des Controls ist 
HWND wnd = msg->hwnd;
while (wnd != NULL)
{
if (m_AdobeWindows.contains(wnd))
{
//Falls der Empfänger also ein Kindfenster unseres Controls ist, dann schlucken wir die Nachricht -> Keine Schutzverletzung im Reader mehr.
return true;
}
wnd = GetParent( wnd );
}
}
if (prevFilter) return prevFilter(message);
return false;
}

Besser wäre natürlich das Fenster-Handle des Readers direkt bei der Initialisierung des Controls zu holen, den Aufwand über die entsprechenden OLE-Interfaces zu gehen wollten wir uns hier sparen.

Hier ist der Adobe-Thread in dem ich die Anregung für die obige Lösung gefunden habe