bachelorthesis/Bachelorarbeit.tex

1603 lines
141 KiB
TeX

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Setup des Dokuments
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\documentclass[12pt,DIV14,BCOR10mm,a4paper,twoside,parskip=half-,headsepline,headinclude]{scrreprt} % Grundgröße 12pt, zweiseitig
% Packages from template
\usepackage[headsepline,automark]{scrpage2} % Seitenköpfe automatisch
\KOMAoptions{headinclude} % Fix
\usepackage[german]{babel} % Sprachpaket für Deutsch (Umlaute, Trennung,deutsche Überschriften)
\usepackage{graphicx,hyperref} % Graphikeinbindung, Hyperref (alles klickbar, Bookmarks)
\usepackage{amssymb} % Math. Symbole aus AmsTeX
\usepackage[utf8]{inputenc} % Umlaute
% Custom packages
\usepackage[autostyle=true,german=quotes]{csquotes} % Anführungszeichen mit \enquote{}
\usepackage{textcomp} % Zusätzliches Package für °C
\usepackage{listings} % Codesnippets
\usepackage{scrhack} % Hack for lstlisting i suspect :-/
\usepackage{xcolor}
\usepackage{float}
\usepackage{soul}
\usepackage{verbatim} % für comment-environment
\usepackage{amsmath}
% Setup für Codeblocks
\lstset{
% Optionen
breaklines=true,
breakatwhitespace=true,
breakautoindent=true,
frame=single,
%framexleftmargin=19pt,
inputencoding=utf8,
%language=awk,
%numbers=left,
%numbersep=8pt,
showspaces=false,
showstringspaces=false,
tabsize=1,
%xleftmargin=19pt,
captionpos=b,
% Styling
basicstyle=\footnotesize\ttfamily,
commentstyle=\footnotesize,
keywordstyle=\footnotesize\ttfamily,
numberstyle=\footnotesize,
stringstyle=\footnotesize\ttfamily,
}
% Hack für Sonderzeichen in Codeblocks
\lstset{literate=%
{Ö}{{\"O}}1
{Ä}{{\"A}}1
{Ü}{{\"U}}1
{ß}{{\ss}}1
{ü}{{\"u}}1
{ä}{{\"a}}1
{ö}{{\"o}}1
{°}{{${^\circ}$}}1
}
% Befehl für TODO-Markierungen
\newcommand{\todo}[1]{\textcolor{blue}{\emph{TODO: #1}}}
% Befehl für Entwürfe und grobe Pläne
\newenvironment{draft}{\par\color{orange}\begin{center}Entwurf / Konzept\end{center}\hrule}{\hrule\par}
% Broken citation needs broken command
\newcommand\mathplus{+}
% Festlegung Kopf- und Fußzeile
\defpagestyle{meinstil}{%
{\headmark \hfill}
{\hfill \headmark}
{\hfill \headmark\hfill}
(\textwidth,.4pt)
}{%
(\textwidth,.4pt)
{\pagemark\hfill Jan Philipp Timme}
{Version 1.0 vom \today \hfill \pagemark}
{Version 1.0 vom \today \hfill \pagemark}
}
\pagestyle{meinstil}
\raggedbottom
\renewcommand{\topfraction}{1}
\renewcommand{\bottomfraction}{1}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Beginn des Dokuments (Titelseite und der ganze Krempel)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{document}
% Titelseite
\thispagestyle{empty}
\includegraphics[width=0.2\textwidth]{res/Wortmarke_WI_schwarz.pdf}
{ ~ \sffamily
\vfill
{\Huge\bfseries Complex Event Processing auf RDF-\\Da\-ten\-strö\-men mit C-SPARQL}
\bigskip
{\Large Jan Philipp Timme
\\[2ex]
Bachelor-Arbeit im Studiengang "`Angewandte Informatik"'
\\[5ex]
\today
}
}
\vfill
~ \hfill
\includegraphics[height=0.3\paperheight]{res/H_WI_Pantone1665.pdf}
\vspace*{-3cm}
% Seite mit Personen und Selbstständigkeitserklärung
\newpage \thispagestyle{empty}
\begin{tabular}{ll}
{\bfseries\sffamily Autor} & Jan Philipp Timme \\
& Matrikelnummer 1271449 \\
& Abteilung Informatik, Fakultät IV \\
& Hochschule Hannover \\
& jan.philipp@timme.it \\[5ex]
{\bfseries\sffamily Erstprüfer} & Prof. Dr. Jürgen Dunkel \\
& Abteilung Informatik, Fakultät IV \\
& Hochschule Hannover \\
& juergen.dunkel@hs-hannover.de \\[5ex]
{\bfseries\sffamily Zweitprüfer} & Jeremias Dötterl, M. Sc. \\
& Abteilung Informatik, Fakultät IV \\
& Hochschule Hannover \\
& jeremias.doetterl@hs-hannover.de
\end{tabular}
\vfill
% fett und zentriert in der Minipage
\begin{center} \sffamily\bfseries Selbständigkeitserklärung \end{center}
Hiermit erkläre ich, dass ich die eingereichte Bachelor-Arbeit
selbständig und ohne fremde Hilfe verfasst, andere als die von mir angegebenen Quellen
und Hilfsmittel nicht benutzt und die den benutzten Werken wörtlich oder
inhaltlich entnommenen Stellen als solche kenntlich gemacht habe.
\vspace*{7ex}
Hannover, den \today \hfill Unterschrift
\pdfbookmark[0]{Inhalt}{contents}
% Inhaltsverzeichnis
\tableofcontents
% Abbildungsverzeichnis
%\listoffigures
% Codeverzeichnis
% \lstlistoflistings
% Tabellenverzeichnis
%\listoftables
\newpage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Hier geht es richtig los mit dem Text!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\chapter{Motivation}\label{cpt:motivation}
Mit der fortschreitenden Digitalisierung von Alltagsgegenständen und ihrer Verbindung mit dem Internet wächst das sogenannte Internet of Things. Dadurch sind auch immer mehr offene Systeme online verfügbar, die ihre Sensordaten und Zustandsinformationen als \emph{RDF}\footnote{Resource Description Framework --- Mehr dazu in Kapitel \ref{cpt:basics}}-Datenstrom anbieten. Diese Ereignisdatenströme liefern durchgehend und hochfrequent Ereignisdaten, sodass innerhalb kurzer Zeit sehr große Datenmengen anfallen, die zwecks Extraktion von Informationen und Auslösen von Reaktionen in kürzester Zeit verarbeitet werden sollen.
Die Ereignisdaten aus diesen Strömen bilden kleine Teile der Realität zumindest nä\-herungs\-wei\-se über die in ihnen enthaltenen Messdaten und Zustandsinformationen ab, sofern sie nicht bedingt durch technischen Defekt oder Messfehler ungültige Daten ent\-hal\-ten und somit vor der weiteren Verarbeitung herausgefiltert werden sollten. Ein weiteres Problem ist die stark begrenzte Gültigkeit von Ereignisdaten: Oft werden sie schon durch ein neu aufgetretenes Ereignis hinfällig und sind nicht mehr aktuell.
Ereignisse haben für sich alleine betrachtet neben einer begrenzten Gültigkeit eine begrenzte Aussagekraft. Daher ist es zum höheren Verständnis der dahinter verborgenen Situation notwendig, sie mit zuvor aufgetretenen Ereignissen in einen Kontext zu setzen. Dadurch können mehrere kleine, hochfrequent auftretende Ereignisse zu einzelnen, niederfrequent auftretenden komplexen Ereignissen aggregiert werden und mittels Mustererkennung höherwertige Informationen aus den Ereignissen extrahiert werden.
\begin{comment}
In Abbildung~\ref{fig:aggregation_patternmatching} wird die Aggregation von Ereignissen sowie die Mustererkennung exemplarisch dargestellt.
\end{comment}
Die Integration von \emph{Domänenwissen}\footnote{Hintergrundwissen für den Kontext der Ereignisverarbeitung, verändert sich während der Verarbeitung nur selten} ist ein weiterer Schritt, der die Brücke zwischen den aus komplexen Ereignissen gewonnenen Kenntnissen und bereits bekannten Fakten schlagen soll, um die gewonnenen Kenntnisse in einen eindeutigen Zusammenhang zu stellen und eine eindeutige Interpretation zu ermöglichen.
Um unter diesen Bedingungen viele Ereignisdatenströme mit hochfrequenten Ereignissen in nahezu Echtzeit zu verarbeiten ist \emph{CEP}\footnote{Complex Event Processing} das Mittel der Wahl: Mit CEP werden die Ereignisse der verschiedenen Datenströme für begrenzte Zeiträume im Speicher vorgehalten und innerhalb von sogenannten \emph{Sliding Windows}\footnote{Mehr dazu in Kapitel~\ref{cpt:cep_intro}} betrachtet. Dabei können Ereignismuster erkannt werden und verschiedene Ereignisse aggregiert werden um neue komplexe Ereignisse zu erzeugen.
Ziel dieser Arbeit ist die Einführung in die Konzepte von CEP und RDF, sowie die Demonstration der praktischen Nutzung der CEP-Engine \enquote{C-SPARQL} zur Verarbeitung von RDF-Datenströmen am Beispiel einer Autoverleihgesellschaft zur Überwachung von Leihfahrzeugen. Auch soll ergründet werden, welche technischen Möglichkeiten existieren um \emph{Reasoning} auf RDF-Datenströmen zu betreiben --- ein Prozess, durch den eine vorhandene Faktensammlung auf Basis von vorgegebener Terminologie automatisch um daraus abgeleitetem Wissen angereichert werden kann.
Diesbezüglich soll ergründet werden, welche CEP-Engines Reasoning bereits implementieren und wie weit ihre technischen Möglichkeiten diesbezüglich reichen. In diesem Kontext stellt Reasoning eine große Herausforderung dar, da die mit einzubeziehenden Ereignisdaten sich kontinuierlich verändern.
\section{Szenario}\label{cpt:scenario}
Das Beispielszenario, welches für diese Arbeit verwendet wird, beinhaltet eine Autoverleihgesellschaft, die ihren Fuhrpark überwachen möchte, um ihren Kunden vergünstigte Ta\-ri\-fe für verschleißarmes Fahrverhalten anbieten zu können. Weiterhin soll auf plötzlich auftretende Probleme an den Leihwagen möglichst schnell reagiert werden können um Schäden zu begrenzen, gefährliche Situationen zu vermeiden und bei Bedarf dem Kunden unverzüglich einen Ersatzwagen oder weitere Serviceleistungen anbieten zu können.
\paragraph{Anforderungen}
Um die Ziele der Autoverleihgesellschaft erreichen zu können, sollen folgende Situationen erkannt werden:
\begin{itemize}
\item Starker Verschleiß eines Fahrzeugs durch unsachgemäßes Fahrverhalten
\item Wartungsbedarf am Fahrzeug, wie beispielsweise:
\begin{itemize}
\item Schäden an den Reifen
\item Vom Fahrzeug selbst erkannte Probleme
\end{itemize}
\item Eintritt eines Unfalls
\item Unbeabsichtigtes Wegrollen von abgestellten Fahrzeugen
\end{itemize}
Um diese Situationen zu erkennen, sollen zwei RDF-Ereignisdatenströme zur späteren Verarbeitung eingerichtet werden und eine Sammlung von Fakten in lokalem Do\-\-nen\-wis\-sen modelliert werden. Diese werden im Folgenden vorerst nur grob beschrieben.
\paragraph{Statusdatenstrom der PKW}
Über diesen Datenstrom sollen die einzelnen PKW kontinuierlich ihre Statuswerte melden:
\begin{itemize}
\item PKW verschlossen (ja/nein)
\item Status des Motors (an/aus)
\item Status der Handbremse (angezogen/gelöst)
\item Momentangeschwindigkeit in km/h
\item Drehzahl des Motors
\item Reifendruck jedes Reifens in bar
\end{itemize}
Besonders wichtige Ereignisse, wie das Aufleuchten der Motorkontrollleuchte oder das Auslösen des Airbags, sollen über diesen Datenstrom separat von den Statusdaten gemeldet werden.
\paragraph{Interaktionsstrom der Kunden}
Wird einem Kunden ein PKW zugewiesen oder gibt ein Kunde einen geliehenen PKW wieder zurück, so soll hierfür ein Ereignis in diesen Datenstrom eingespeist werden. Diese Ereignisse sollen immer die eindeutige Kundennummer und die Nummer des geliehenen Fahrzeugs enthalten, um eine eindeutige Zuordnung vornehmen zu können.
\paragraph{Domänenwissen}
Um die Ereignisdaten aus den beiden beschriebenen Datenströmen bei der Verarbeitung in einen eindeutigen Kontext setzen zu können, wird lokal ver\-füg\-ba\-res Hintergrundwissen benötigt. Es soll die Zuordnung von Kunden zu den von ihnen geliehenen PKW aufbauen, die einzelnen PKW konkreten Automodellen zuordnen und somit eine konkrete Interpretation der von den PKW gemeldeten Daten ermöglichen.
Das Domänenwissen soll in diesem Szenario folgende Informationen enthalten:
\begin{itemize}
\item Eindeutige Zuordnung von Kundennummer zu einem Kunden
\item Kundendaten wie Name und Telefonnummer
\item Eindeutige Zuordnung von Fahrzeugnummer zu Automodell
\item Wissen über Automodelle:
\begin{itemize}
\item Empfohlene Motordrehzahlbereiche für verschleißarmen Betrieb während der Fahrt
\item Vorgeschriebener Reifendruck
\end{itemize}
\end{itemize}
\todo{GRAFIK: Grobes Informationsnetzwerk zur Veranschaulichung der Zu\-sam\-men\-hän\-ge zwischen den drei Elementen (Ströme + Hintergrundwissen).}
\chapter{Grundlagen}\label{cpt:basics}
Nachdem in Kapitel~\ref{cpt:motivation} das Vorhaben dieser Arbeit grob beschrieben wurde, folgt nun eine Einführung in die dafür benötigten Grundlagen. Da die später zu verarbeitenden Ereignisdatenströme im RDF-Format vorliegen werden, soll zunächst eine Einführung in das semantische Web zeigen, wie RDF zur Modellierung und Beschreibung von Wissen eingesetzt werden kann, und welche Möglichkeiten dadurch geboten werden. Anschließend sollen die grundlegenden Konzepte von CEP mit Hinblick auf die Verarbeitung von RDF-Datenströmen erläutert werden.
\section{Einführung: RDF im semantischen Web}
Das sogenannte \enquote{semantische Web} ist ein großes Anwendungsgebiet für RDF-Daten und deren Verlinkung. In diesem Abschnitt soll erläutert werden, was RDF ist, wofür es eingesetzt wird, und wie man in RDF vorliegende Daten verwenden und weiter verarbeiten kann.
\subsection{RDF im semantischen Web}\label{cpt:rdf-semantic-web}
Das Resource Description Framework (RDF) wird im semantischen Web zur Mo\-del\-lie\-rung und Repräsentation von Wissen verwendet. RDF-Daten bestehen aus einer Menge von Tripeln, welche sich aus den drei Komponenten Subjekt, Prädikat und Objekt in genau dieser Abfolge zusammensetzen um eine Aussage zu formen. Jeder dieser drei Bestandteile eines Tripels kann durch einen eindeutigen Uniform Resource Identifier (URI\footnote{Der URI wird in RFC 3986 beschrieben. Anstelle eines URI kann auch ein IRI (Internationalized Resource Identifier) verwendet werden --- die internationalisierte Form des URI nach RFC 3987.}) identifiziert werden. Lediglich in der Position \enquote{Objekt} eines Tripels kommen auch sehr häufig sogenannte \emph{Literale} vor um konkrete Datenwerte beispielsweise in Form von Zeichenketten oder Ganzzahlen zu repräsentieren.
\begin{lstlisting}[caption={Ein RDF-Tripel in Turtle-Notation},label={lst:sample_rdf_triple}]
<http://example.org/carSim/objects/Car#23> <http://example.org/carSim/carSimulationOntology#isCarModel> <http://example.org/carSim/objects/CarModel#42> .
\end{lstlisting}
Das in Listing~\ref{lst:sample_rdf_triple} enthaltene Tripel ordnet das Car-Subjekt \texttt{\#23} über das Prädikat \texttt{isCarModel} dem Objekt CarModell \texttt{\#42} zu. Abbildung~\ref{fig:triple_spo} illustriert neben diesem Tripel auch die Verknüpfung des Car-Subjektes über das Prädikat \texttt{hasNickname} zu dem Literalwert \enquote{Alf}.
\begin{figure}[htbp]
\centering
\includegraphics[width=\textwidth]{img/triple-spo.pdf}
\caption{Prädikate verknüpfen ein Subjekt mit Objekt und Literalwert}
\label{fig:triple_spo}
\end{figure}
\paragraph{Turtle-Notation mit Prefixen}
Wie anhand des Beispiels aus Listing~\ref{lst:sample_rdf_triple} erkennbar ist, ist die explizite Notation für Tripel aufgrund der häufigen Nennung von vollständigen URIs wenig platzsparend und für große Datenmengen somit nicht empfehlenswert. Da es neben XML noch andere Repräsentationsformate für RDF-Daten gibt, bietet sich die Nutzung einer Notation an, die eine Nutzung von Prefixen erlaubt. Nach einer einmaligen Definition innerhalb eines Kontextes (zum Beispiel einer Datei) können diese Prefixe dann für den gesamten Kontext verwendet werden und verringern somit erheblich den Bedarf an Speicherplatz bei erhöhter Übersichtlichkeit für Menschen.
Listing~\ref{lst:sample_rdf_triple_with_prefix} zeigt die Notation von Tripeln im Turtle\footnote{Siehe auch die Spezifikation der Turtle-Notation nach \cite{w3c:turtle}}-Format unter Verwendung von Prefixen.
\begin{lstlisting}[caption={Das Tripel aus Listing~\ref{lst:sample_rdf_triple} mit Prefixen},label={lst:sample_rdf_triple_with_prefix}]
@prefix car: <http://example.org/carSim/objects/Car#> .
@prefix carModel: <http://example.org/carSim/objects/CarModel#> .
@prefix carOnt: <http://example.org/carSim/carSimulationOntology#> .
car:23 carOnt:isCarModel carModel:42 .
\end{lstlisting}
Aufgrund der Flexibilität dieser Struktur ist es möglich, nahezu jede Art von Informationen auf Tripel abzubilden, wie Listing~\ref{lst:sample_rdf_data} an einem Beispiel zeigt.
\begin{lstlisting}[caption={RDF-Daten beschreiben einen Fahrer, ein PKW und dessen Modell},label={lst:sample_rdf_data}]
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix car: <http://example.org/carSim/objects/Car#> .
@prefix carModel: <http://example.org/carSim/objects/CarModel#> .
@prefix driver: <http://example.org/carSim/objects/Driver#> .
@prefix carOnt: <http://example.org/carSim/carSimulationOntology#> .
car:0 rdf:type carOnt:Car .
car:0 carOnt:isCarModel carModel:0 .
carModel:0 rdf:type carOnt:CarModel .
carModel:0 carOnt:minimumMotorRPM 2000 .
carModel:0 carOnt:maximumMotorRPM 4300 .
carModel:0 carOnt:minimumTirePressure 2.9 .
carModel:0 carOnt:maximumTirePressure 3.2 .
carModel:0 carOnt:requiresDriverLicense "B"^^xsd:string .
driver:0 rdf:type carOnt:Driver .
driver:0 carOnt:hasName "Max Mustermann"^^xsd:string .
driver:0 carOnt:hasPhoneNumber "+49 111 123456789"^^xsd:string .
driver:0 carOnt:hasDriverLicense "B"^^xsd:string .
car:0 carOnt:isDrivenBy driver:0 .
driver:0 carOnt:drives car:0 .
\end{lstlisting}
\paragraph{Objektklassen und -instanzen}
In RDF kann ein Subjekt grundsätzlich eine Instanz von mehreren Objektklassen sein. Um anzugeben, von welcher Objektklasse ein Tripel-Subjekt eine Instanz ist, wird das Prädikat \texttt{rdf:type} verwendet. Synonym dazu kann anstelle von \texttt{rdf:type} auch das Prädikat \texttt{a} verwendet werden, wie Listing~\ref{lst:sample_rdf_a_predicate} zeigt.
\begin{lstlisting}[caption={Das Prädikat \texttt{a} ist synonym zu \texttt{rdf:type}},label={lst:sample_rdf_a_predicate}]
@prefix car: <http://example.org/carSim/objects/Car#> .
@prefix carOnt: <http://example.org/carSim/carSimulationOntology#> .
car:0 a carOnt:Car .
\end{lstlisting}
Abhängig von den Objektklassen eines Subjektes können Terminologiedaten\footnote{Wie zum Beispiel RDFS-Vokabular oder OWL-Ontologien} nun einschränken, wie eine Objektinstanz mit anderen Objektinstanzen oder Werten verbunden werden darf. So kann beispielsweise definiert werden, dass das Prädikat \texttt{carOnt:drives} als Subjekt nur Instanzen der Klasse \texttt{carOnt:Driver} und als Objekt nur Instanzen der Klasse \texttt{carOnt:Car} zulässt, da die umgekehrte Richtung nicht sinnvoll wäre. (Für die umgekehrte Richtung wäre etwa \texttt{carOnt:isDrivenBy} sinnvoll.)
\paragraph{Graphen}
Da innerhalb des semantischen Web angestrebt wird, in RDF vorliegende Informationen gemeinsam zu nutzen und miteinander vernetzen zu können, werden RDF-Tripel meist als Quadrupel (oder kurz \enquote{Quad}) gehandhabt, in denen als zusätzliche Information der sogenannte Graph genannt wird, in dem die Tripel enthalten sind. Ein Graph wird durch eine URI identifiziert und dient als Namensraum für die Tripel, die er enthält. Dies vereinfacht die gleichzeitige Nutzung von mehreren Datenquellen, da jedes Tripel eindeutig einem Graphen zugeordnet werden kann und innerhalb von Abfragen spezifisch Tripel aus verschiedenen Graphen selektiert werden können.
\todo{GRAFIK: Verbildlichung durch mehrere mit URI gekennzeichneten Container, die ein paar Tripel enthalten, die aufeinander verweisen.}
\paragraph{RDF-Schema für einfache Terminologiedaten}
Das \enquote{RDF-Schema} (kurz RDFS\footnote{Für eine detailreiche Einführung in RDFS siehe auch \cite{hitzler:semanticweb}[Kapitel 3.4]}) dient zur Spezifikation von Schemawissen durch die Definition von Objektklassen und Prädikaten, welche in hierarchischen Verhältnissen zueinander stehen können\footnote{Mittels \texttt{rdfs:subclassOf} und \texttt{rdfs:subpropertyOf} können hierarchische Verhältnisse zwischen verschiedene Properties beziehungsweise Objektklassen definiert werden.}. Betrachtet man bei\-spiels\-wei\-se die Daten aus Listing~\ref{lst:sample_rdf_data}, so ist in diesem Kontext für die Verwendung des Prädikats \texttt{carOnt:drives} offensichtlich, dass es nur zusammen mit einem Subjekt der Klasse \texttt{Driver} und einem Objekt der Klasse \texttt{Car} verwendet werden sollte, um eine sinn\-volle Aussage zu ergeben. Eine solche Regel kann mit Hilfe von RDFS definiert werden, wie Listing~\ref{lst:sample_rdfs_data} zeigt.
\begin{lstlisting}[caption={Definition der Klassen \texttt{Car} und \texttt{Driver} sowie des Prädikats \texttt{drives} in RDFS},label={lst:sample_rdfs_data}]
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix carOnt: <http://example.org/carSim/carSimulationOntology#> .
carOnt:Car rdf:type rdfs:class .
carOnt:Driver rdf:type rdfs:class .
carOnt:drives rdf:type rdf:Property .
carOnt:drives rdfs:domain carOnt:Driver .
carOnt:drives rdfs:range carOnt:Car .
\end{lstlisting}
In Listing~\ref{lst:sample_rdfs_data} werden zunächst die beiden Klassen \texttt{carOnt:car} und \texttt{carOnt:Driver} definiert. Im darauf folgenden Absatz wird dann das Merkmal \texttt{carOnt:drives} definiert und über \texttt{rdfs:domain} auf Subjekte der Klasse \texttt{Driver} und mit \texttt{rdfs:range} auf Objekte der Klasse \texttt{Car} eingeschränkt. Trifft man in diesem Kontext nun Tripel mit einem Prädikat \texttt{carOnt:drives} an, so kann man anhand der zugehörigen RDFS-Daten bereits erkennen, womit man es zutun hat.
\paragraph{OWL-Ontologien}
In OWL (Web Ontology Language\footnote{Die Abkürzung lautet tatsächlich OWL und nicht WOL. Mehr dazu ist unter \url{http://lists.w3.org/Archives/Public/www-webont-wg/2001Dec/0169.html} nachzulesen.}) formulierte Ontologien werden im semantischen Web neben RDF-Schemata sehr häufig zur Strukturierung von RDF-Daten verwendet. Ähnlich wie RDFS definieren OWL-Ontologien ein Vokabular mit logischen Do\-\-nen\-objekt\-klas\-sen und bestimmt für diese Objektklassen Prädikate und Attribute, um bestimmte Sachverhalte eindeutig abbilden zu können. Allerdings bietet OWL weitere, mächtigere Sprachkonstrukte, um feiner granulare Regeln für Objektklassen und Attribute aufzustellen. Attribute können beispielsweise transitiv oder symmetrisch wirkend definiert werden, wodurch die Verwendung solcher Attribute deutlich mehr implizite Aussagekraft hat. So ergibt sich aus transitiven Verknüpfungen wie \enquote{Max ist verwandt mit Anne} und \enquote{Anne ist verwandt mit Moritz} implizit auch die Aussage \enquote{Max ist verwandt mit Moritz}.
Auch sind spezifische Einschränkungen für Attributtypen auf Datentypebene möglich. Eine OWL-Ontologie für Listing~\ref{lst:sample_rdf_data} könnte beispielsweise eine Objektklasse \texttt{Driver} definieren, auf welche das eigens hierfür definierte Prädikat \texttt{hasName} mit einem Attribut vom Typ \texttt{xsd:string} angewandt werden kann. Durch die Möglichkeiten dieser Restriktionen können RDF-Daten aus der Welt einer Ontologie --- ähnlich wie bei einem relationalen Datenbankschema --- eindeutig auf inhaltlicher Ebene strukturiert werden.
\paragraph{ABox und TBox}
Bei der Modellierung von Wissen mit Hilfe von Beschreibungslogiken, zu denen auch OWL und RDFS zählen, werden die formulierten Aussagen in zwei Gruppen unterteilt\cite{hitzler:semanticweb}[Kapitel 6.1]: Die Assertion Box (ABox) und die Terminology Box (TBox). Während die TBox Aussagen mit terminologischem Schemawissen wie Definitionen von Objektklassen, Prädikaten und ihren Verhältnissen zueinander enthält, beinhaltet die ABox sogenanntes \emph{assertionales Instanzwissen}\cite{hitzler:semanticweb}[Kapitel 6.1], welches aus Aussagen über konkrete Klasseninstanzen und deren Merkmale und Beziehungen besteht. In den Aussagen aus der ABox wird dabei das in der TBox definierte Vokabular verwendet.
Ein Beispiel für eine TBox ist Listing~\ref{lst:sample_rdfs_data}, in welcher Objektklassen definiert werden, die innerhalb der ABox aus Listing~\ref{lst:sample_rdf_data} verwendet werden, um konkrete Instanzen dieser Objektklassen zu beschreiben.
\todo{GRAFIK: Ein wenig visualisiertes Wissen, welches TBox und ABox-Inhalte getrennt voneinander darstellt. (Eine Ebene mit Klassen und Attributen, eine andere Ebene mit konkreten Klasseninstanzen und deren Merkmalen)}
\paragraph{Kombination verschiedener Ontologien}
Natürlich ist es möglich, mehrere verschiedene Ontologien gleichzeitig zu verwenden. Diese Flexibilität ermöglicht beispielsweise, dass eine bereits in RDF-Daten abgebildete Person durch beliebige Informationen unter Nutzung von Vokabular aus weiteren Ontologien ergänzt werden kann. Natürlich können die Informationen einer in RDF abgebildeten Person auch in verschiedenen, für andere Parteien geläufigen Strukturen verfügbar gemacht werden. Innerhalb einer Ontologie kann auf die Objektklassen und Attribute zurückgegriffen werden, die in anderen Ontologien definiert werden. Dies ermöglicht neben Erweiterungen für spezifische Zwecke auch das Übersetzen von Wissen zwischen verschiedenen Ontologien durch die Definition von äquivalenten Objektklassen und Attributen.
\paragraph{Open World Assumption}
\emph{Sollte es nötig sein}, für eigene Terminologie eine Ontologie zu erzeugen, so ist es wichtig hervorzuheben, dass für in RDF abgebildete Fakten nahezu immer die Annahme gilt, dass diese Daten nicht vollständig sind und somit nicht alle real existierenden Fakten auch in RDF erfasst sind. Die meisten existierenden Ontologien respektieren diese Annahme und verzichten auf die Definition von expliziten Regeln, die über die konkrete Bedeutung der \emph{Abwesenheit} von bestimmten Fakten entscheiden würden. (In der Welt der relationalen Datenbanksysteme gibt es eine ähnliche Problematik in Zusammenhang mit der Verwendung des Schlüsselworts \texttt{NULL} und dessen konkreter Bedeutung.)
\subsection{Anreicherung von RDF-Daten durch Reasoning}\label{cpt:reasoning}
Durch den Einsatz von Terminologiewissen in Form von OWL-Ontologien oder RDF-Schemata ergibt sich die Möglichkeit, die Fakten aus der ABox automatisch um über die Terminologie abgeleitetes Wissen anzureichern. Diesen Prozess bezeichnet man als \emph{Reasoning}.
In diesem Prozess werden aus den in RDF vorliegenden Fakten (Assertion Box) und den in den verwendeten Ontologien definierten Objektklassen, Regeln und Zusammenhängen (Terminology Box) neues Wissen abgeleitet \cite{hitzler:semanticweb} und die lokale Datenbasis damit angereichert. So können beispielsweise implizite Klassentypen errechnet werden: \enquote{Ein PKW ist auch ein Fahrzeug, da PKW eine Unterklasse der Klasse Fahrzeug ist.}. Analog hierzu ist es auch möglich, übergeordnete Beziehungen aus vorhandenen Beziehungen abzuleiten: \enquote{Max fährt sein Auto. Da man zum Fahren eines Autos (meist) in dem Auto sitzen muss, folgt daraus: Max sitzt in seinem Auto.}.
Enthält eine Ontologie strukturelle Informationen über Fahrer, PKW und Attribute bezüglich technischer Daten über PKW-Modelle, so ist es beispielsweise möglich auf Basis der Fakten aus Listing~\ref{lst:sample_rdf_data} zusätzliche Attribute der Fahrer wie \enquote{isDrivingCarModel} oder der PKW wie \enquote{hasEmergencyContactNumber} zu errechnen. Dieses funktioniert natürlich nur, falls in den Fakten bekannt ist, dass ein Fahrer ein Fahrzeug fährt und somit eine Verbindung zwischen dem Fahrer und dem Fahrzeug existiert. Limitiert werden die Möglichkeiten des Reasoning ebenfalls durch die \emph{Open World Assumption}. Daher sollten für Reasoning nur explizit bekannte Fakten genutzt werden: Nur weil in Listing~\ref{lst:sample_rdf_data} keine Informationen über weitere PKW oder Fahrer vorhanden sind heißt das nicht, dass diese nicht existieren oder Einfluss auf die bekannten Fakten haben könnten. Weiterführende Beispiele zu den Möglichkeiten von OWL Reasoning finden sich unter \cite{man:owl}.
Da Ontologien auch genutzt werden können, um Wissen aus den Strukturen einer Ontologie in die Struktur einer anderen Ontologie zu übersetzen, kann ein Reasoner die daraus resultierende Übersetzung direkt errechnen und der lokalen Datenbasis hinzufügen. Dadurch steht Abfragen, die schon auf die Ziel-Ontologie zugeschnitten sind, ein viel größerer Informationspool zur Verfügung, aus dem das Abfrageergebnis berechnet werden soll.
Die Vorteile von Reasoning erkauft man sich durch einen nicht unerheblichen Einsatz von Rechenleistung, da im Prozess des Reasoning eine Menge von zusätzlichen Daten entsteht, für die zusätzlich zu den bereits vorhandenen Daten die Regeln aller genutzten Ontologien berücksichtigt werden müssen. Behandelt man lediglich statische Daten, die sich kaum bis garnicht ändern, so ist der nötige Aufwand für Reasoning übersichtlich und liegt auch für große Mengen von Daten und Ontologien in einem akzeptablem Rahmen. Ändern sich jedoch häufig Daten, so muss für das Subset der veränderten Daten der Reasoning-Prozess erneut durchgeführt werden um eine vollständig aktuelle Datenbasis zu erhalten. Dies ist der Grund, weshalb an effizientem Reasoning auf RDF-Ereignisdatenströmen aktuell immer noch geforscht wird.
\subsection{Abfrage von RDF-Daten via SPARQL}\label{cpt:sparql_intro}
Die Abfrage von Wissen aus RDF-Daten erfolgt über die Abfragesprache \emph{SPARQL} (\enquote{SPARQL Protocol And RDF Query Language}), welche in diesem Abschnitt grob erläutert wird. Eine detaillierte Beschreibung von SPARQL ist unter \cite{w3c:sparql} nachzulesen.
Im Gegensatz zu Abfragesprachen von relationalen Datenbanksystemen wie SQL ist es mit SPARQL möglich, Daten über verschiedene Datenquellen wie Tripel- oder Quadstores\footnote{Analog zu relationalen Datenbanksystemen für Relationen ist ein Tripel- beziehungsweise Quadstore ein Speicher für RDF-Tripel beziehungsweise RDF-Quads.} hinweg miteinander zu verknüpfen. Auch ist im Gegensatz zu SQL keine spezielle Anpassung der Abfragen an ein Datenbankschema notwendig; lediglich die Art und Weise, wie die angeforderten Daten miteinander in Verbindung stehen, ist für SPARQL-Abfragen wichtig. Kenntnisse über das verwendete Vokabular (RDF-Schema oder OWL-Ontologien) können jedoch bei der Formulierung der Abfragen hilfreich sein. Im Folgenden zeigt Listing~\ref{lst:sample_sparql_query} eine einfache Abfrage auf den Daten aus Listing~\ref{lst:sample_rdf_data}.
\begin{lstlisting}[caption={Abfrage der maximal zulässigen Motordrehzahl des Auto-Subjektes Nr. 0 aus Daten von Listing~\ref{lst:sample_rdf_data}},label={lst:sample_sparql_query}]
PREFIX carOnt: <http://example.org/carSim/carSimulationOntology#>
PREFIX car: <http://example.org/carSim/objects/Car#>
SELECT ?maxMotorRPM
WHERE {
car:0 carOnt:isCarModel ?carModel .
?carModel carOnt:maximumMotorRPM ?maxMotorRPM .
}
\end{lstlisting}
\paragraph{Selektion von Daten}
Listing~\ref{lst:sample_sparql_query} zeigt, dass SPARQL in der groben Grundstruktur eine Ähnlichkeit zu SQL aufweist; allerdings sind bedingt durch die Struktur der Daten (Relationen bei SQL gegenüber Tripel und Quadrupel bei SPARQL) große Unterschiede in der Gestaltung der Abfragen zu finden. Zunächst werden analog zur Turtle-Notation Prefixe notiert, die innerhalb der Abfrage verwendet werden sollen. In der \texttt{WHERE}-Klausel werden Tripel mit Platzhaltern verwendet, um aus dem vorhandenen Datenbestand die Tripel zu isolieren, die auf das angegebene Muster passen. So wird in diesem Beispiel zunächst ein Tripel gesucht, welches als Subjekt \texttt{car:0} gesetzt hat und das Prädikat \texttt{carOnt:isCarModel} verwendet, welches auf das zu dem Auto zugehörige Automodell-Subjekt zeigt. Ergibt sich ein Treffer, so wird der Objekt-Teil des gefundenen Tripels in den Platzhalter \texttt{?carModel} eingefügt und für die Suche des zweiten Tripels des SPARQL-Queries verwendet.
Für das gefundene \texttt{?carModel} wird nun ein Tripel gesucht, welches für \texttt{?carModel} das Prädikat \texttt{carOnt:maximumMotorRPM} nutzt, um die Angabe der maximalen Drehzahl für dieses Automodell zu definieren. Wird hierfür ebenfalls ein Treffer gelandet, so wird der Platzhalter \texttt{?maxMotorRPM} mit dem dazugehörigen Wert gefüllt und kann dann in der \texttt{SELECT}-Klausel selektiert werden. Für jede Tripelkombination, die auf die in der \texttt{WHERE}-Klausel formulierten Beschreibung passt, ergibt sich nun ein Ergebnis, für welches die in der \texttt{SELECT}-Klausel angegebenen Felder zurückgegeben werden --- in diesem Fall also lediglich ein Ergebnis mit dem Wert \enquote{4300}.
\paragraph{Konstruktion von Daten durch Abfragen}
Neben \texttt{SELECT} unterstützt SPARQL auch den Befehl \texttt{CONSTRUCT}. Dieser ermöglicht die direkte Konstruktion von neuen Tripeln aus vorgegebenen Tripeln mit Platzhaltern, welche mit den Ergebnissen der Abfrage gefüllt werden. Listing~\ref{lst:sample_sparql_construct} zeigt die beispielhafte Erzeugung von Tripeln auf Basis der Daten aus Listing~\ref{lst:sample_rdf_data}, welche über das Prädikat \texttt{carOnt:motorRPMTolerance} Auskunft über die Größe des akzeptablen Drehzahlbereiches der Automodelle geben sollen.
\begin{lstlisting}[caption={Konstruktion von neuen Tripeln auf Basis des Wissens aus Listing~\ref{lst:sample_rdf_data}},label={lst:sample_sparql_construct}]
PREFIX carOnt: <http://example.org/carSim/carSimulationOntology#>
PREFIX car: <http://example.org/carSim/objects/Car#>
CONSTRUCT {
?carModel carOnt:motorRPMTolerance ?rpmTolerance .
} WHERE {
?carModel rdf:type carOnt:CarModel .
?carModel carOnt:minimumMotorRPM ?minMotorRPM .
?carModel carOnt:maximumMotorRPM ?maxMotorRPM .
BIND(?maxMotorRPM - ?minMotorRPM AS ?rpmTolerance) .
}
\end{lstlisting}
Wie in Listing~\ref{lst:sample_sparql_construct} gezeigt, können einfache Operationen in SPARQL-Abfragen durch\-ge\-führt werden, deren Ergebnisse über die \texttt{BIND}-Anweisung in einen vorgegebenen Platzhalter \texttt{?rpmTolerance} eingesetzt werden. In diesem Beispiel wurde die Differenz zwischen der maximalen und der minimalen empfohlenen Motordrehzahl eines Automodells ausgerechnet und ein neues Tripel mit dieser Information generiert.
\section{Einführung in Complex Event Processing}\label{cpt:cep_intro}
Von Transaktionen im Handel über Messereignisse von Sensoren bis hin zu Benutzerinteraktionen auf Webseiten entstehen täglich eine Vielzahl von Ereignisdaten, die für einen begrenzten Zeitraum einen Teil der echten Welt abbilden. Um aus diesen großen Datenmengen durch Erkennung von Mustern oder durch Aggregation von Daten schnellstmöglich höherwertige Informationen gewinnen zu können, ist Complex Event Processing (CEP) das Mittel der Wahl. Wie der Begriff CEP bereits andeutet, geht es dabei um die Verarbeitung von komplexen Ereignissen. Im folgenden Abschnitt wird hierfür ein kurzer Einstieg in die Grundlagen von CEP gegeben. Für eine detailreiche Erläuterung und die beispielhafte Anwendung der CEP-Engine \enquote{Esper} sei auf \cite{hsh:cep} verwiesen.
\paragraph{CEP-Engine}
Um Complex Event Processing durchführen zu können, wird eine CEP-Engine benötigt. Eine CEP-Engine ist eine Software, welche Ereignisdatenströme konsumiert und diese durch die Auswertung benutzerdefinierter CEP-Regeln verarbeitet. Aufgrund der großen Datenvolumen, die eine CEP-Engine verarbeiten muss, werden Ereignisse nur für die Dauer der Verarbeitung\footnote{Dieser Parameter hängt meist von der Größe der verwendeten Sliding Windows ab.} im Speicher gehalten und nicht persistiert.
\paragraph{Ereignisse}
Im Rahmen von Complex Event Processing werden Ereignisdaten als Instanzen von Ereignistypen betrachtet. Während ein Ereignistyp für eine bestimmte Klasse von Vorkommnissen grundlegende Eigenschaften und Attribute definiert, re\-prä\-sen\-tiert eine Ereignisinstanz ein konkretes Ereignis dieses Ereignistypen\cite{hsh:cep}. Diese trägt neben inhaltlichen Informationen über den Vorgang durch den sie ausgelöst wurde auch eine eindeutige ID sowie einen Zeitstempel mit sich. Während der Zeitstempel den Zeitpunkt der Ereignisauslösung angibt, dient die ID zur eindeutigen Abgrenzung von anderen Ereignissen, die vom selben Ereignistyp sind und zum selben Zeitpunkt entstanden sind. Da es bedingt durch Übertragunglatenz und weitere technische Randbedingungen möglich ist, dass die Ereignisdaten zeitverzögert bei der CEP-Engine ankommen, wird der Zeitstempel ebenfalls benötigt, um die zeitlichen Beziehungen zwischen den Ereignissen zu erhalten.
Jedes Ereignis trägt abhängig von seinem Ereignistypen eine geringe Menge von Daten mit sich, die für das Ereignis spezifische Informationen enthalten. Dies können beispielsweise Daten von Sensoren, Angaben über eine Benutzersitzung oder Statusdaten eines Systems sein. Diese Daten sind jedoch nur \emph{Momentaufnahmen} und verlieren mit fortschreitender Zeit meist sehr schnell an Gültigkeit. Listing~\ref{lst:sample_abstract_car_status_event} zeigt beispielhaft eine Instanz des Ereignistypen \texttt{Car\allowbreak Status\allowbreak Event}.
\begin{lstlisting}[caption={Exemplarische Statusmeldung eines PKW in abstrakter Notation},label={lst:sample_abstract_car_status_event}]
CarStatusEvent(ID=2342, timestamp=1344829400, relatedCarID=11, speed=63)
\end{lstlisting}
Dafür treten diese primitiven Ereignisse häufig mit einer sehr hohen Frequenz auf, da ein komplexer Vorgang während seiner Durchführung eine Vielzahl von Ereignissen auslösen kann. Beobachtet man beispielsweise einen aus dem Stand anfahrenden PKW bis zur Erreichung einer Geschwindigkeit von 30km/h, so würde man zusätzlich zu periodisch gemeldeten Messwerten von Sensoren aus dem Motorraum und Informationen über Gangwechsel des Getriebes eine Flut von Informationen darüber erhalten, wie die Pedale durch den Fahrer bedient wurden oder wie das Lenkrades eingeschlagen wurde.
Natürlich können nicht nur externe Komponenten als Quelle von Ereignissen dienen. Viele CEP-Engines unterstützen die Erzeugung von Ereignisdaten und deren Injektion in die eigene Ereignisverarbeitung. So können durch CEP-Regeln gewonnene Erkenntnisse direkt Einfluss auf die weitere Verarbeitung nehmen, indem sie als neue Ereignisse in die Verarbeitung eingespeist werden.
\paragraph{CEP-Regeln}
Zur Erläuterung von CEP-Regeln wird eine aus Kapitel 3 von \cite{hsh:cep} entlehnte, abstrakte Sprache eingeführt, anhand derer die einzelnen Sprachkonstrukte von CEP-Regeln erläutert werden sollen.
Eine CEP-Regel besteht zunächst aus zwei Teilen: Zuerst definiert der \texttt{CONDITION}-Teil Ereignismuster, die in dem Ereignisdatenstrom gesucht werden sollen, sowie spezifische Bedingungen, die für auf das Muster passende Ereignisse erfüllt sein müssen.
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION (... Ereignismuster ...)
... weitere Bedingungen ...
\end{lstlisting}
Sind alle Bedingungen im \texttt{CONDITION}-Teil erfüllt, so \enquote{matcht} die Regel\cite{hsh:cep}. Im darauffolgenden \texttt{ACTION}-Teil wird eine Reihe von Aktionen definiert, die ausgeführt werden sollen, sobald die Bedingungen der Regel eintreffen. Dies kann beispielsweise die Erzeugung eines neuen Ereignisses oder das Anstoßen eines Dienstes sein. Da die Daten der Ereignisse, auf die der \texttt{CONDITION}-Teil gepasst hat, für die weitere Verarbeitung von Interesse ist, stehen sie im \texttt{ACTION}-Teil zur Verfügung.
\begin{lstlisting}[mathescape=true,label={},caption={}]
ACTION
... auszulösende Aktionen ...
\end{lstlisting}
Matcht eine Regel, so werden die in ihr definierten Aktionen ausgeführt --- die Regel feuert\cite{hsh:cep}. Zusammengefasst sieht eine CEP-Regel wie folgt aus:
\begin{lstlisting}[mathescape=true,label={lst:abstract_cep_rule_basics},caption={Grundgerüst einer CEP-Regel}]
CONDITION (... Ereignismuster ...)
... weitere Bedingungen ...
ACTION
... auszulösende Aktionen ...
\end{lstlisting}
\paragraph{Erkennung von Ereignismustern}
Komplexe Vorgänge kann man durch Muster in den Ereignisfolgen erkennen, die sie bei ihrer Durchführung ausgelöst haben. Hierbei spielen Ereignisfolgen und die zeitlichen Beziehungen zwischen Ereignissen eine Rolle. Um ein \enquote{bedeutungsvolles Ereignismuster} zu erkennen, wird eine CEP-Regel benötigt, die dieses Muster in ihrem \texttt{CONDITION}-Teil beschreibt.
Ein Beispiel für eine Ereignisfolge, welche durch unsachgemäß abgestellte PKW ausgelöst werden kann, könnte so aussehen:
\begin{itemize}
\item Ereignistyp A: Motor wurde abgestellt
\item \st{Ereignistyp B: Handbremse wurde angezogen} (trat \textbf{nicht} auf!)
\item Ereignistyp C: PKW wurde verriegelt
\end{itemize}
Um diese Folge von Ereignistypen mit einer CEP-Regel erkennen zu können, müssen die Bedingungen, die dieses Ereignismuster impliziert, mit Hilfe von \emph{Ereignisalgebra} beschrieben werden. Diese wurde größtenteils aus \cite{hsh:cep} entnommen und beschreibt mit Operatoren und Funktionen Bedingungen für eine Menge von Ereignissen.
Eine Ereignisfolge ist Teilmenge eines Ereignisdatenstromes. Sie kann nach dem Schema $a_1b_1a_2c_1$ notiert werden, wobei $a_i$ die $i$-te Instanz des Ereignistypen A bezeichnet\cite{hsh:cep}. Zur Unterscheidung von Ereignistypen und Instanzen werden für Instanzen kleine Buchstaben, für Ereignistypen Großbuchstaben verwendet. Die Typen von Ereignissen werden zur Beschreibungen von Bedingungen in Ereignismustern verwendet.
Um ein Ereignismuster zu beschreiben werden Operatoren aus der Ereignisalgebra nach \cite{hsh:cep} benötigt:
\begin{itemize}
\item Der \textbf{Sequenzoperator} $X \rightarrow Y$ dient zur Formulierung zeitlicher Ab\-häng\-ig\-kei\-ten zwischen zwei Ereignistypen. So beschreibt der Ausdruck eine Ereignisfolge, in der zuerst ein Ereignis vom Typ X auftritt, gefolgt von einer Ereignisinstanz des Typ Y. Die Ereignisfolge $c_1x_1c_2b_1y_1$ erfüllt diese Bedingung.
\item Die \textbf{boolschen Operatoren} $\wedge$ und $\vee$: Sie definieren \textbf{keine zeitlichen} Ab\-häng\-ig\-kei\-ten zwischen Ereignissen, bestimmen aber, \emph{welche} Ereignisse in einer Folge vorkommen dürfen. So trifft der Ausdruck $(A \vee B)$ auf Ereignisfolgen zu, die entweder ein Ereignis vom Typ A oder ein Ereignis vom Typ B enthalten. Die Folgen $c_1b_1d_1d_2$, $a_1d_1d_2c_1c_2$ und $b_1c_1a_1$ passen auf diesen Ausdruck. Der Ausdruck $(A \wedge B)$ hingegen trifft nur auf Ereignisfolgen wie $d_1c_1a_1b_1c_2$ zu, in denen beide Ereignistypen vorkommen, wobei die zeitliche Reihenfolge keine Rolle spielt.
\item Den \textbf{Negationsoperator} $\neg X$ erlaubt keine Vorkommnisse des Ereignistyps X in der Ereignisfolge und ergibt nur in Kombination mit dem Sequenzoperator oder unter Verwendung von Sliding Windows (siehe nachfolgenden Abschnitt über Sliding Windows) Sinn. So würde der Ausdruck $(\neg A)$ für die Folge $b_1c_1b_2$ zutreffen, nicht jedoch für die Folge $b_1b_2a_1c_1$.
\end{itemize}
Nimmt man nun die oben angegebene Beispielfolge und formuliert daraus ein Muster in Ereignisalgebra, so erhält man: $(A)\rightarrow (\neg B) \rightarrow (C)$. Um auf die für dieses Muster passenden Ereignisinstanzen im Rahmen der weiteren Ereignisverarbeitung zugreifen zu können, müssen diese mit dem \texttt{AS}-Operator einer Variable zugewiesen werden können. Somit sieht dieses Muster nun wie folgt aus:
\[(A\ AS\ a)\rightarrow (\neg B) \rightarrow (C\ AS\ c)\]
Dieses Muster kann man nun in einer CEP-Regel einsetzen, jedoch ist noch nicht garantiert, dass die auf das Muster passenden Ereignisinstanzen sich auf den \emph{selben} PKW beziehen --- es fehlen noch \emph{Kontextbedingungen}. Um diese zu definieren muss innerhalb der CEP-Regel Zugriff auf die Daten der gefundenen Ereignisinstanzen möglich sein. Um dies zu ermöglichen, gibt es zwei Hilfsmittel:
\begin{itemize}
\item Die \textbf{Aliasanweisung} \texttt{AS} ermöglicht die Definition eines Variablennamens innerhalb des Ereignismusters, der die für einen Ereignistypen gefundene Ereignisinstanz enthalten soll. So würde das Ereignismuster $(A\ AS\ eventA)$ dafür sorgen, dass die gefundene Ereignisinstanz vom Typ A in der Variable \texttt{eventA} für den Rest der CEP-Regel verfügbar ist.
\item Um nun auf Attribute einer Ereignisinstanz zuzugreifen, wird der \textbf{Attributoperator} \enquote{\textbf{.}} verwendet: Gegeben sei eine Ereignisinstanz, welche das Attribut \texttt{pkwID} enthält und in der Variable \texttt{eventA} enthalten ist, so würde der Ausdruck \[eventA.pkwID\] den Wert dieses Attributs zurückgeben.
\end{itemize}
Geht man davon aus, dass die Ereignistypen A und C aus dem obigen Ereignismuster die ID des PKW, von dem das Ereignis ausgelöst wurde, in dem Attribut \texttt{pkwID} enthalten, so kann man mit der Kontextbedingung
\[a.pkwID\ =\ c.pkwID\]
sicherstellen, dass die CEP-Regel nur für Ereignisfolgen matcht, die von dem selben PKW ausgelöst wurden und das Muster erfüllen. Listing~\ref{lst:abstract_cep_rule_two} zeigt diese CEP-Regel.
\begin{lstlisting}[mathescape=true,label={lst:abstract_cep_rule_two},caption={CEP-Regel mit Ereignismuster und Kontextbedingungen}]
CONDITION ($(A\ AS\ a)\rightarrow (\neg B) \rightarrow (C\ AS\ c)$)
$\wedge$ a.pkwID = c.pkwID
ACTION
... auszulösende Aktionen ...
\end{lstlisting}
\todo{GRAFIK: Mustererkennung grob veranschaulichen?}
\paragraph{Sliding Windows und Tumbling Windows}
Um die großen Mengen von Ereignisdaten aus einem Datenstrom effizient verarbeiten zu können, werden sie in einem Fenster fester Größe betrachtet. Die Größe eines solchen Fensters wird häufig mit Zeiteinheiten wie Sekunden angegeben; selten wird die Größe durch eine Anzahl von Ereignissen angegeben, die das Fenster enthalten kann.
Ein \emph{Sliding Window} wird nach erfolgter Auswertung seines Inhalts um eine festgelegten Größe verschoben um aktuellere Ereignisse zu betrachten, wobei die älteren Ereignisse zugunsten der neuen Ereignisse aus dem Fenster herausfallen. Die Ergebnisse von CEP-Regeln verändern sich somit nach jedem Verschieben des Ereignisfensters. Das \emph{Tumbling Window} ist ein Sonderfall des Sliding Window, bei dem die Fenstergröße und das Intervall, um das es verschoben wird, gleich sind. Es wird somit quasi \enquote{umgeklappt}, sodass alle zuvor in ihm enthaltenen Ereignisse herausfallen und aktuellere Ereignisse in das nun leere Fenster gefüttert werden. Da somit ein Ereignis nur einmal in einem Tumbling Window Platz findet, kann es auch nur \emph{ein einziges Mal} Einfluss auf die Auswertung nehmen.
\todo{GRAFIK: Sliding Window vs Tumbling Window}
Da je nach Anforderung einer CEP-Regel ein bestimmtes Ereignisfenster zweckmäßig ist, ist es notwendig, dies in der CEP-Regel festlegen zu können. Dafür werden die Angaben der Fenstergröße (\texttt{WindowSize}) und des Intervalls, in dem das Fenster verschoben wird (\texttt{StepSize}), wie folgt verwendet:
\begin{lstlisting}[mathescape=true,label={},caption={}]
[WindowSize:15min,StepSize:10s]
\end{lstlisting}
Natürlich ist es auch möglich, ein Ereignisfenster auf Basis von Ereignissen zu definieren:
\begin{lstlisting}[mathescape=true,label={},caption={}]
[WindowSize:150events,StepSize:10events]
\end{lstlisting}
Die Definition des verwendeten Ereignisfensters wird am \texttt{CONDITION}-Teil der CEP-Regel platziert, sodass diese nun wie folgt aussieht:
\begin{lstlisting}[mathescape=true,label={lst:abstract_cep_rule_with_window},caption={CEP-Regel mit Definition eines Ereignisfensters}]
CONDITION (Ereignismuster)[WindowSize:15min,StepSize:10s]
... weitere Bedingungen ...
ACTION
... auszulösende Aktionen ...
\end{lstlisting}
\paragraph{Aggregation von Ereignissen}\label{cpt:cep_aggregate_events}
Eine weitere Möglichkeit zur Auswertung von Ereignisdaten ist die Aggregation der Attributdaten von Ereignissen zu höherwertigeren Ereignissen. Hierbei werden Ereignisse gleichen Typs innerhalb eines Sliding Windows betrachtet und mit Ereignismustern und Kontextbedingungen vorgefiltert. Dann können sie nach ihren Attributwerten gruppiert werden und diese dann mittels Aggregationsfunktionen zusammengefasst werden. So können Trends oder Kennzahlen aus Ereignisdaten gewonnen werden, die zur Interpretation der Ereignisdaten beitragen können.
\todo{GRAFIK: Aggregation visuell zeigen?}
In einer CEP-Regel können Aggregation mit dem Konstrukt
\begin{lstlisting}
AGGREGATE(event, "attribute", {groupByVariable}, function)
\end{lstlisting}
notiert werden, wobei \texttt{event} eine Ereignisinstanz enthält, \texttt{'attribute'} den Namen des zu aggregierenden Attributs nennt, \texttt{\{groupByVariable\}} eine Liste von Variablen enthält, nach denen die Ereignistypen zu gruppieren sind, und \texttt{function} die Funktion nennt, mit der die Daten aggregiert werden sollen. Das Ergebnis der Aggregationsfunktion kann direkt in Kontextbedingungen verwendet werden oder mit dem \texttt{AS}-Operator einer lokalen Variable zugewiesen werden. Die folgenden Funktionen stehen zur Verfügung:
\begin{itemize}
\item COUNT: Zählt alle Ereignisinstanzen; die Angabe des Attributnamen wird nicht berücksichtigt.
\item SUM: Bildet Summe über Attributwerte
\item AVG: Bildet Mittelwert über Attributwerte
\item MIN: Ermittelt minimalen Attributwert
\item MAX: Ermittelt maximalen Attributwert
\end{itemize}
Um die Verwendung von Aggregationsfunktionen innerhalb von CEP-Regeln zu demonstrieren, folgt nun ein Beispiel: Innerhalb eines 15 Minuten Zeitfensters soll die durchschnittliche Geschwindigkeit von PKW bestimmt werden, die einen Messpunkt an einer Straße passieren. Unterschreitet die Durchschnittsgeschwindigkeit den Wert von 60km/h, so soll die Regel feuern.
\begin{lstlisting}[mathescape=true,label={lst:abstract_cep_rule_four},caption={Beispiel zur Nutzung von \texttt{AGGREGATE()}}]
CONDITION ($MeasureEvent\ AS\ m$)
$\wedge$ AGGREGATE(m, "speed", {m.relatedCar}, AVG) AS averageSpeed
$\wedge$ averageSpeed < 60
ACTION
... auszulösende Aktionen ...
\end{lstlisting}
Die in Listing~\ref{lst:abstract_cep_rule_four} gezeigte CEP-Regel demonstriert die Umsetzung dieses Beispiels. Der Aufruf
\begin{lstlisting}
AGGREGATE(m, "speed", {m.relatedCar}, AVG) AS averageSpeed
\end{lstlisting}
sorgt hierbei dafür, dass alle durch das Ereignismuster gefundenen Ereignisinstanzen in der Variable \texttt{m} für die Aggregation vorgesehen werden. Die Werte des Attributes \enquote{speed} werden für alle gefunden Ereignisinstanzen nach \texttt{m.relatedCar} gruppiert, und mit der Funktion \texttt{AVG} ausgewertet. Dadurch wird die resultierende Durchschnittsgeschwindigkeit aller gemessenen PKW-Geschwindigkeiten in der Variable \texttt{averageSpeed} abgelegt, welche dann für die weitere Auswertung verwendet wird.
\paragraph{Integration von Domänenwissen}
Die Kontextbedingungen der CEP-Regeln können auch Bezug auf \emph{Domänenwissen} nehmen, welches von der Anwendung lokal vorgehalten wird.
Hat man bei der Ereignisverarbeitung bereits fast alle Register gezogen, so kommt die Integration des Domänenwissens ins Spiel. Alle Fakten, die für die auszuwertenden Ereignisdatenströme und deren Kontext bekannt sind, liegen hier vor. Beispiele dafür wären:
\begin{itemize}
\item Wo ein Sensor positioniert ist und was er misst
\item Von welchem Modell ein zu überwachendes Fahrzeug ist und welche bekannten Grenzwerte für den Betrieb eingehalten werden müssen
\item Wer ein überwachtes Fahrzeug gerade verwendet
\end{itemize}
Das Domänenwissen kann somit verwendet werden, um einer durch Ereignisdaten be\-schrie\-be\-nen Situation einen eindeutigen Kontext zuzuordnen und somit die Interpretation der Daten stark zu erleichtern. Wurde durch die Ereignisverarbeitung beispielsweise festgestellt, dass die durchschnittliche Drehzahl eines Motors bei 4200 Umdrehungen pro Minute liegt, so könnte über das Domänenwissen nachgeschlagen werden, in welchem Fahrzeug der betroffene Motor montiert ist, von welchem Typen dieses Fahrzeug ist, und letztendlich ob die Motordrehzahl für diesen Fahrzeugtypen im zulässigen Bereich liegt.
Um aus der CEP-Regel auf dieses Wissen zuzugreifen, kann innerhalb einer CEP-Regel das Konstrukt \texttt{lookup(\emph{key}, \emph{param...})} verwendet werden, wobei \texttt{key} für die aus dem Domänenwissen abzufragende Information steht und über \texttt{param...} beliebig viele Parameter erlaubt werden, die den Wissenszugriff spezifizieren. Parameter können dabei Variablen aus der CEP-Regel sein. Der Aufruf kann direkt in Kontextbedingungen integriert werden, sodass die Formulierung dieser Bedingungen dadurch nicht erschwert wird. Soll beispielsweise die maximal zulässige Motordrehzahl für ein in \texttt{fooEvent.carModel} hinterlegtes Automodell nachgeschlagen werden, so kann der Ausdruck
\begin{lstlisting}
lookup("maxMotorRPMForModel", fooEvent.carModel)
\end{lstlisting}
die hierfür benötigten Daten aus dem Domänenwissen nachschlagen und liefern. Analog zu der Aggregationsfunktion \texttt{AGGREGATE()} kann auch das Ergebnis von \texttt{lookup()} mittels des \texttt{AS}-Operators in einer CEP-Regel in eine Variable abgelegt werden.
\paragraph{Auslösen von Aktionen}
Um mit einer CEP-Regel nun auf die durch sie erkannten Sachverhalte reagieren zu können, können nahezu beliebige Aktionen im \texttt{ACTION}-Teil der Regel definiert werden. Diese lassen sich hauptsächlich in zwei Kategorien einteilen:
\begin{itemize}
\item Erzeugung eines höherwertigen Ereignis mit den gewonnen Erkenntnissen und dessen Wiedereinspeisung in den Verarbeitungsprozess
\item Anstoßen eines externen Dienstes beziehungsweise Ausführen von Programmcode
\end{itemize}
Sehr häufig werden die Auswertungsergebnisse einer CEP-Regel als höherwertiges Ereignis wieder in den Verarbeitungsprozess injiziert. Dies ist gerade dann nützlich, wenn durch eine mehrstufige Verarbeitung mit weiteren CEP-Regeln detailliertere Ergebnisse erzielt werden können. Um in dem \texttt{ACTION}-Teil einer CEP-Regel ein neues Ereignis auszulösen, wird mit dem Schlüsselwort \texttt{new} ein Ereignistyp wie zum Beispiel \texttt{CarAccelerationEvent} wie folgt instanziiert, wobei seine Attributwerte als Parameter übergeben werden:
\begin{lstlisting}
new CarAccelerationEvent(carID=eventA.pkwID, speed=eventC.speed-eventB.speed)
\end{lstlisting}
Um den Anstoß eines externen Dienstes beziehungsweise das Ausführen von Programmcode zu notieren, wird ein Methodenaufruf notiert, der zur Verdeutlichung mit \texttt{call} beginnt und Parameter übergeben bekommen kann. Der Aufruf eines \texttt{Push\allowbreak Message\allowbreak Service}, der einen Parameter \texttt{msg} erwartet, könnte wie folgt aussehen:
\begin{lstlisting}
callPushMessageService(msg="PKW mit ID"+eventA.pkwID+" hat ein Problem gemeldet.")
\end{lstlisting}
Erweitert man nun die CEP-Regel aus Listing~\ref{lst:abstract_cep_rule_two} mit einem Dienstaufruf, der den Fahrer des PKW auf Basis der \texttt{pkwID} benachrichtigen würde, so könnte dies so aussehen:
\begin{lstlisting}[mathescape=true,label={lst:abstract_cep_rule_three},caption={CEP-Regel benachrichtigt Fahrer des PKW im Notfall}]
CONDITION ($(A\ AS\ a)\rightarrow (\neg B) \rightarrow (C\ AS\ c)$)
$\wedge$ a.pkwID = c.pkwID
ACTION
callNotifyCarDriver(pkwID=a.pkwID)
\end{lstlisting}
Mit den in diesem Kapitel vorgestellten Werkzeugen können abstrakte CEP-Regeln formuliert werden, die später zur Implementierung in die CEP-Sprache einer CEP-Engine übersetzt werden können.
Insgesamt liegt die Herausforderung von CEP darin, in kürzester Zeit große Datenströme von Ereignissen mit Hintergrundwissen anzureichern, diese zu höherwertigen Ereignissen zu kombinieren und bestimmte Muster zu finden, sowie die Ergebnisse mit möglichst geringer Verzögerung in Echtzeit ausgeben zu können oder Reaktionen einzuleiten.
\chapter{Vergleich aktueller RDF-fähiger CEP-Engines}\label{cpt:engine_comparison}
Nachdem ein kurzer Einstieg in die Welt von RDF und CEP gegeben wurde, soll nun eine CEP-Engine ausgewählt werden, mit der RDF-Ereignisdatenströme verarbeitet werden können, um das Beispielszenario aus Kapitel~\ref{cpt:scenario} im weiteren Verlauf der Arbeit umsetzen zu können. In diesem Kapitel werden drei CEP-Engines vorgestellt, die einen C-SPARQL-Dialekt implementieren --- eine Form der Abfagesprache SPARQL, welche um Sprachkonstrukte erweitert wurde, die speziell auf die Verarbeitung von RDF-Datenströmen zugeschnitten sind. Eine der Engines soll in diesem Kapitel zur Umsetzung des Beispielszenarios aus Kapitel~\ref{cpt:scenario} ausgewählt werden.
\section{EP-SPARQL mit der ETALIS-Engine}
Die ETALIS-Engine stellt zur Formulierung von CEP-Regeln neben der eigenen Sprache ELE (\enquote{Etalis Language for Events}) die Sprache EP-SPARQL (\enquote{Event Processing SPARQL}) zur Verfügung\cite{ep:etalis}. Ein erstes Paper\cite{ep:etalis} über die Engine erschien 2009; die Autoren lassen darauf schließen, dass die Engine vom FZI Forschungszentrum Informatik (Karlsruhe, Deutschland), dem Karlsruher Institut für Technologie (Karlsruhe, Deutschland), und der Stony Brook University (New York, USA) entwickelt wurde.
Bei der Engine handelt es sich um einen in Prolog implementierten Prototyp\cite{ep:unified}, der neben der Integration von lokalem Domänenwissen auch Reasoning auf Basis von gegebenem RDFS-Vokabular auf den Ereignisdaten unterstützt\cite{ep:etalis}. Dabei werden Fakten und CEP-Regeln direkt übersetzt um die vorhandene Prolog-Umgebung zur Auswertung nutzen zu können\cite{hsh:integrating}. Neben einer Schnittstelle für die Sprache Prolog stehen auch Schnittstellen für Java, C und C\# zur Verfügung\cite{ep:etalis}, jedoch wird für den Betrieb der Engine zusätzlich immer eine Prolog-Laufzeitumgebung benötigt.
\section{Das CQELS-Framework}
Das CQELS-Framework (Continuous Query Evaluation over Linked Stream) wurde in Zusammenarbeit von dem Insight Centre for Data Analytics an der National University of Ireland (Galway, Irland), dem Institute of Information Systems an der Vienna University of Technology (Wien, Österreich) und dem Institut für Telekommunikationssysteme an der TU-Berlin (Berlin, Deutschland) entwickelt; ein erstes Paper über CQELS erschien 2011\cite{cqels:native}. Es wurde in Java implementiert und unterstützt die Einbindung von lokalem Domänenwissen in die Ereignisverarbeitung\cite{cqels:stream}. Über Reasoning-Fähigkeiten konnten bisher keine Angaben gefunden werden, jedoch kann nicht ausgeschlossen werden, dass Reasoning mit CQELS möglich ist.
Es kann als Grundlage zum Aufbau einer sehr leistungsfähigen RDF-fähigen CEP-Engine verwendet werden\cite{cqels:stream}, da es im Gegensatz zu vielen anderen RDF-fähigen CEP-Engines die eigentliche Datenverarbeitung nicht eine externe Engine delegieren, sondern eine eigene Implementierung verwendet. Dadurch reduziert sich die benötigte Rechenleistung, da so keine übersetzende Schnittstelle zu einer externen Engine benötigt wird \cite{cqels:native}.
Eine im Paper \cite{cqels:stream} vorgestellte Lösung\footnote{Unter \url{http://graphofthings.org/debs2015/cqels.zip} ist ein VirtualBox-Image mit der Lösung zum Ausprobieren erhältlich. (Größe ca. 1,1 GiB)} zur Verarbeitung von RDF-Er\-eig\-nis\-da\-ten\-strö\-men mit einem C-SPARQL-Dialekt konsumiert die Ereignisdaten über einen aus einer Datei lesenden CSV-Reader und schreibt die Ergebnisse der Verarbeitung über einen CSV-Writer in eine Datei, wobei die Engine intern mit RDF-Daten arbeitet, die von Reader und Writer jeweils konvertiert werden. Da es sich bei CQELS um ein Framework handelt\cite{cqels:native} ist im Gegensatz zu einer fertigen CEP-Engine zur Umsetzung eines Szenarios mit einem erhöhten Aufwand zu rechnen. Dafür punktet es im Vergleich zu klassischen, nicht RDF-fähigen CEP-Engines wie beispielsweise Esper mit einer hohen Performance\cite{cqels:stream}.
\section{C-SPARQL-Engine}
Die C-SPARQL-Engine wurde vom Polytechnikum Mailand (Italien) entwickelt; ein erstes Paper erschien im Jahr 2009\cite{barbieri:csparql}. Sie wurde zur Verarbeitung von RDF-Datenströmen im Rahmen eines Forschungsprojektes\cite{barbieri:csparql} in der Sprache Java entwickelt und bietet einen eigenen \emph{C-SPARQL-Dialekt} zur Formulierung von CEP-Regeln an, in denen direkt Bezug auf lokal vorhandenes Domänenwissen genommen werden kann. Weiterhin beinhaltet sie eine Implementierung von Reasoning auf RDFS-Vokabular, wie \cite{barbieri:reasoning} beschreibt.
Ein \enquote{Hello World}-Softwarepaket zur Demonstration der Engine, welches unter \url{http://streamreasoning.org/resources/c-sparql} zum Download angeboten wird, beinhaltet ein Eclipse/Maven-Projekt, welches Einsteigern einen ersten Überblick verschafft und leicht für eigene Zwecke angepasst werden kann. Da die Engine auf Software wie Apache Jena zum Speichern von RDF-Daten, Sesame zur Analyse und Abfrage von RDF-Daten via SPARQL, und die bekannte CEP-Engine Esper aufbaut, sind für die verwendeten Basistechnologien bereits gute Dokumentationen erhältlich.
\section{Auswahl der Engine für die Arbeit}
Da in dieser Arbeit die Verarbeitung von RDF-Ereignisdatenströmen anhand einer konkreten CEP-Engine erläutert werden soll, muss nun eine Entscheidung für eine CEP-Engine gefällt werden. Um einen möglichst einfachen Einstieg in die Thematik zu bekommen, soll die vorgestellte Engine möglichst wenig zusätzlichen Aufwand hervorrufen. Um eine CEP-Engine auszuwählen, die für Einsteiger der Thematik möglichst leicht in der Handhabung ist, sind folgende Kriterien für die Auswahl der Engine verwendet worden:
\begin{itemize}
\item Verfügbarkeit von einsteigerfreundlicher Dokumentation
\item Existierender Beispielcode zur Verwendung
\item Verwendung einer einzigen, weit verbreiteten Programmiersprache
\item Transparente Implementierung der Engine durch Verwendung bekannter Technologien
\end{itemize}
Die Bewertung dieser Kriterien ist für EP-SPARQL\footnote{Quellen über Etalis/EP-SPARQL: \cite{ep:unified} und \cite{ep:etalis}}, CQELS\footnote{Paper über CQELS: \cite{cqels:native} und \cite{cqels:stream}} und C-SPARQL\footnote{Erläuternde Werke über C-SPARQL: \cite{barbieri:csparql},\cite{barbieri:reasoning} und \cite{barbieri:querying}} wie folgt ausgefallen:
\begin{tabular}{p{4cm}||p{3.3cm}|p{3.3cm}|p{3.3cm}}
& EP-SPARQL & CQELS & C-SPARQL \\
\hline\hline
Dokumentation & Mehrere Quellen er\-läu\-tern Features (o) & Dokumente über Umsetzung eines Projekts und Performance (o) & Mehrere Quellen mit Er\-läu\-te\-run\-gen (+) \\
\hline
Beispielcode & Archiviertes Projekt auf Google-Code \dots (o) & Projekt als VirtualBox (o) & Kom\-men\-tier\-tes Bei\-spiel\-pro\-jekt mit ver\-schie\-de\-nen Szenarien (+) \\
\hline
Programmiersprache & Prolog, Java (-) & Java (+) & Java (+) \\
\hline
Technologien & Prolog, eigene (o) & eigene (-) & Apache Jena, Sesame, Esper (+)\\
\hline\hline
Ergebnis & (-) & (o) & (++++) \\
\end{tabular}
Unter Berücksichtigung dieser Kriterien fiel die Wahl auf die CEP-Engine C-SPARQL; sie wird im weiteren Lauf der Arbeit vorgestellt, um die Verarbeitung von RDF-Er\-eig\-nis\-da\-ten\-strö\-men durchzuführen und das Beispielszenario aus Kapitel~\ref{cpt:scenario} umzusetzen.
\chapter{CEP auf RDF-Datenströmen anhand der C-SPARQL Engine}
Nachdem die Wahl der CEP-Engine im vorherigen Kapitel auf die C-SPARQL-Engine gefallen ist, sollen in diesem Kapitel nun ihre Möglichkeiten zur Verarbeitung von RDF-Datenströmen beleuchtet werden. Hierfür wird die Abbildung von Ereignisdaten mit RDF erläutert und die in Kapitel~\ref{cpt:cep_intro} eingeführte abstrakte Sprache für CEP-Regeln verwendet werden, um die Fähigkeiten von C-SPARQL-Queries zu demonstrieren --- die auf SPARQL aufgebaute Regelsprache der C-SPARQL-Engine.
\section{Ereignisinstanzen in RDF-Datenströmen}
Ereignisse --- genauer: Ereignisinstanzen --- werden aufgrund der Nutzung von RDF-Datenströmen als Transportmedium nun durch RDF-Tupel beschrieben. Diese tragen neben den typischen Inhalten von Tripeln (Subjekt, Prädikat und Objekt) nun eine vierte Information mit sich: den Zeitstempel, zu dem das Ereignis ausgelöst wurde. Da die Tupel nun mit dem Zeitstempel insgesamt vier Angaben enthalten, werden sie als \emph{Quadrupel} bezeichnet.
Um die Ereignisquadrupel trotz identischer Ereignistypen oder Zeitstempel voneinander unterscheiden zu können, ist es nötig, die Ereignisse mit einer eindeutigen ID zu versehen. Um dies zu erreichen, werden die einzelnen Ereignisinstanzen als eigene, separate Subjekte in RDF repräsentiert. Dadurch erhalten alle Quadrupel eines Ereignisses eine eindeutige Zuordnung zu ihrem Ereignissubjekt.
Um dies zu verdeutlichen folgt nun ein kleines Beispiel. Gegeben seien zwei beispielhafte Ereignisse mit Statusdaten über einen PKW. Listing~\ref{lst:sample_abstract_event_data} zeigt diese beiden Ereignisse in abstrakter Notation.
\begin{lstlisting}[caption={Zwei Statusereignisse eines PKW in abstrakter Notation},label={lst:sample_abstract_event_data}]
CarStatusEvent(ID=324, timestamp=1344829400, relatedCarID=0, speed=63)
CarStatusEvent(ID=325, timestamp=1344829405, relatedCarID=0, speed=75)
\end{lstlisting}
Wie aus Listing~\ref{lst:sample_abstract_event_data} zu erkennen ist, ist jede Ereignisinstanz quasi ein eigenes Objekt, welches alle für sich relevanten Daten entweder direkt oder als Zeiger auf andere Objekte enthalten kann. Um diese über RDF-Datenströme zu transportieren, müssen diese Ereignisinstanzen mit RDF-Quadrupeln beschrieben werden, wie Listing~\ref{lst:sample_event_rdf_quads} zeigt.
\begin{lstlisting}[caption={RDF-Quadrupel mit Zeitstempeln beschreiben zwei Ereignisse eines PKW},label={lst:sample_event_rdf_quads}]
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix carOnt: <http://example.org/carSim/carSimulationOntology#> .
@prefix event: <http://example.org/carSim/objects/event#> .
(1344829400) event:324 rdf:type carOnt:CarStatusEvent .
(1344829400) event:324 carOnt:relatedCar car:0 .
(1344829400) event:324 carOnt:speed 63 .
(1344829405) event:325 rdf:type carOnt:CarStatusEvent .
(1344829405) event:325 carOnt:relatedCar car:0 .
(1344829405) event:325 carOnt:speed 75 .
\end{lstlisting}
Wie in Listing~\ref{lst:sample_event_rdf_quads} zu sehen ist, wurde die ID der Ereignisinstanzen zur Generierung der Subjekt-URI verwendet, die in den Quadrupeln verwendet werden, um die Ereignisse zu beschreiben. Aus dem Ereignistyp wurde eine Aussage, die die Subjekt-URI via \texttt{rdf:type} mit einer RDFS-Objektklasse verknüpft, die diesen Ereignistypen repräsentieren soll. Alle weiteren Attribute der Ereignisinstanzen wurden über eigens hierfür definierte RDFS-Prädikate dem Ereignissubjekt zugeordnet. Die Angabe des PKW, auf den sich die Ereignisinstanzen beziehen, wurde durch eine direkte Ver\-knü\-pfung der Ereignissubjekte mit dem zugehörigen PKW-Subjekt modelliert. Die PKW-Subjekte sind hierbei im Domänenwissen hinterlegt; dazu später mehr in Kapitel~\ref{cpt:integrating_knowledge}.
\section{C-SPARQL als Sprache für CEP-Regeln}
Um die Ereignisdatenströme mit RDF-Quadrupeln nun in der C-SPARQL-Engine verarbeiten zu können, werden im Verarbeitungsprozess in C-SPARQL-Queries formulierte CEP-Regeln benötigt. Die Konstrukte und Fähigkeiten von C-SPARQL sollen in diesem Abschnitt erläutert werden. Weiterführend gibt es neben den Grundlagen zu SPARQL aus Kapitel~\ref{cpt:sparql_intro} noch ein Dokument der W3C\cite{w3c:sparql}. Eine detailliertere Erläuterung von C-SPARQL mit Beispielabfragen ist unter \cite{barbieri:csparql} nachzulesen.
Da C-SPARQL die Abfragesprache SPARQL lediglich erweitert, sind alle gültigen Abfragen aus SPARQL automatisch auch gültige C-SPARQL-Abfragen\cite{barbieri:csparql}. Bevor nun die einzelnen Features von C-SPARQL erläutert werden, folgt nun ein ein gültiger Beispiel-Query, der als Grundlage der Erläuterungen dienen soll. Die Angabe von Prefixen mit der \texttt{PREFIX}-Klausel ist natürlich auch in C-SPARQL-Abfragen erforderlich, wird in diesem Abschnitt aber aus Gründen der Übersichtlichkeit weggelassen.
\begin{lstlisting}[label={lst:example_csparql_query},caption={Beispielhafter C-SPARQL-Query}]
REGISTER QUERY exampleQuery AS
SELECT ?a ?c
FROM STREAM <http://example.org> [RANGE 5s STEP 1s]
WHERE {
?a a <http://example.org/type/A> .
?c a <http://example.org/type/C> .
BIND(f:timestamp(?a,rdf:type,<http://example.org/type/A>) AS ?tsa)
BIND(f:timestamp(?c,rdf:type,<http://example.org/type/C>) AS ?tsc)
FILTER(?tsa < ?tsc)
FILTER NOT EXISTS {
?b a <http://example.org/type/B> .
BIND(f:timestamp(?b,rdf:type,<http://example.org/type/B>) AS ?tsb)
FILTER(?tsa < ?tsb && ?tsb < ?tsc)
}
}
\end{lstlisting}
Betrachtet man den Beispielquery aus Listing~\ref{lst:example_csparql_query}, so ist erkennbar, dass C-SPARQL die Registrierung des Queries an der Engine erfordert. Diese wird nach dem folgenden Schema durchgeführt, wobei \texttt{QName} einen Namen für die Abfrage angibt und \texttt{Query} für die gesamte, restliche Abfrage steht:
\begin{lstlisting}[label={lst:register_query_csparql},caption={Registrierung von Queries in C-SPARQL}]
'REGISTER' ('QUERY'|'STREAM') QName 'AS' Query
\end{lstlisting}
Vorerst werden C-SPARQL-Queries unter Angabe des Schlüsselwortes \texttt{QUERY} registriert, mit dem Schlüsselwort \texttt{STREAM} wird sich später im Abschnitt~\ref{cpt:register_stream} beschäftigt.
Somit wurde die in Listing~\ref{lst:example_csparql_query} angegebene Abfrage als \texttt{QUERY} unter dem Namen \enquote{exampleQuery} an der Engine registriert.
\paragraph{Ereignisdatenströme in Sliding Windows}
Um mit der Verarbeitung von Ereignisdatenströmen beginnen zu können, müssen die für die Verarbeitung benötigten RDF-Datenströme in der CEP-Regel angegeben werden. Zwischen den Klauseln \texttt{SELECT} und \texttt{WHERE} gibt das Konstrukt \texttt{FROM [NAMED] STREAM <\emph{streamUri}> [\emph{window}]} den zu verwendenden Ereignisdatenstrom an. Es definiert neben der in \texttt{streamUri} zu hinterlegenden URI des zu konsumierenden RDF-Datenstroms auch das an Stelle von \texttt{window} zu definierende Sliding Window, in dem dieser betrachtet werden soll\cite{barbieri:csparql}. Die Angabe des Schlüsselworts \texttt{NAMED} ist hierbei optional und funktioniert ähnlich wie in herkömmlichen SPARQL-Queries indem die Herkunft der einströmenden Tripel in Variablen vorgehalten wird, die über die \texttt{GRAPH}-Klausel verwendet werden können.
Die Angabe des Sliding Windows ist zwingend erforderlich, da die kontinuierlich einströmenden Ereignisdaten zur Verarbeitung auf eine endliche Datenmenge reduziert werden müssen. Weiterhin ist die Spezifikation des Sliding Window sinnvoll, da je nach Anforderungen der CEP-Regel Ereignisse aus bestimmten Zeiträumen für die Verarbeitung interessant sind --- je nach Zweck der CEP-Regel können diese Zeiträume sich stark unterscheiden.
Um ein Sliding Window in C-SPARQL zu definieren, wird die folgende Grammatik (entnommen aus \cite{barbieri:querying}) ab dem Einstiegspunkt \texttt{Window} verwendet:
\begin{lstlisting}[mathescape=true]
Window $\rightarrow$ 'RANGE' LogicalWindow | 'RANGE' PhysicalWindow
LogicalWindow $\rightarrow$ Number TimeUnit WindowOverlap
TimeUnit $\rightarrow$ 'ms' | 's' | 'm' | 'h' | 'd'
WindowOverlap $\rightarrow$ 'STEP' Number TimeUnit | 'TUMBLING'
PhysicalWindow $\rightarrow$ 'TRIPLES' Number
\end{lstlisting}
Mit dieser Grammatik können drei verschiedene Sorten von Sliding Windows definiert werden:
\begin{itemize}
\item Das reguläre \textbf{Sliding Window} erfasst einen definierten Zeitraum und wird nach jeder Auswertung um seine definierte Schrittgröße weitergeschoben. Die definierte Schrittgröße darf nicht größer als die Größe des Sliding Windows sein. Der Ausdruck
\begin{lstlisting}
[WindowSize:15min,StepSize:5s]
\end{lstlisting}
definiert ein Sliding Window, welches Ereignisdaten über einen Zeitraum von 15 Minuten beinhaltet und nach jeder erfolgten Auswertung in 5 Sekunden-Schritten verschoben wird. Übersetzt man diesen Ausdruck mit der obigen Grammatik in einen C-SPARQL-Ausdruck, so erhält man \texttt{[RANGE 15m STEP 5s]}.
\item Das \textbf{Tumbling Window} ist ein Sonderfall des Sliding Window, da es nach jeder erfolgten Auswertung um seine eigene Größe verschoben wird. Ein solches Ereignisfenster, welches Ereignisdaten über den Zeitraum einer Stunde fasst und nach jeder erfolgten Auswertung um diese Größe verschoben wird, kann durch den Ausdruck
\begin{lstlisting}
[WindowSize:1h,StepSize:1h]
\end{lstlisting}
beschrieben werden. In der Abfragesprache C-SPARQL kann dieses Tumbling Window durch den Ausdruck \texttt{[RANGE 1h TUMBLING]} definiert werden. Durch das Verschieben des Tumbling Windows um seine eigene Größe kommt es nicht zu Überlappungen zwischen den Fenstern, sodass eine Ereignisinstanz immer nur in einem Tumbling Window und somit auch nur in einer Auswertung vorkommt.
\item Das \textbf{Physical Window} nutzt keine Zeiträume zur Festlegung seiner Größe, sondern eine feste Anzahl von Ereignisinstanzen, die es enthalten kann. Da die C-SPARQL-Engine bei dieser Art von Ereignisfenster keine Angabe einer Schrittgröße erlaubt\cite{barbieri:csparql}, um die das Fenster nach jeder Auswertung verschoben wird, wird davon ausgegangen, dass das Fenster immer nur um eine Ereignisinstanz verschoben wird. Der folgende Ausdruck definiert ein Physical Window, welches 150 Ereignisinstanzen erfasst und immer nur um eine Ereignisinstanz verschoben wird:
\begin{lstlisting}
[WindowSize:150events,StepSize:1event]
\end{lstlisting}
Übersetzt man diese Definition in die C-SPARQL-Abfragesprache, so erhält man \texttt{[RANGE TRIPLES 150]}.
\end{itemize}
Um einen RDF-Ereignisdatenstrom innerhalb einer C-SPARQL-Abfrage verwenden zu können, ist also lediglich die Nutzung des \texttt{FROM STREAM}-Konstrukts notwendig. Gegeben sei ein RDF-Datenstrom, der innerhalb eines Sliding Window über einen Zeitraum von 30 Minuten bei einer Schrittgröße von 5 Minuten betrachtet werden soll, der unter der URI \path{http://example.org/exampleStream} erreichbar ist. Um ihn in eine C-SPARQL-Abfrage unter den genannten Bedingungen einzubringen, muss also die Anweisung
\begin{lstlisting}
FROM STEAM <http://example.org/exampleStream> [RANGE 30m STEP 5m]
\end{lstlisting}
verwendet werden.
\paragraph{Mustererkennung}
Um nun innerhalb der \texttt{WHERE}-Klausel von C-SPARQL-Abfragen Ereignismuster definieren zu können und diese mit Kontextbedingungen weiter zu verfeinern, müssen zunächst die Operatoren der Ereignisalgebra aus Kapitel~\ref{cpt:cep_intro} in Elemente der C-SPARQL-Sprache übersetzt werden. Um dies zu tun, werden folgende Ausdrücke aus C-SPARQL benötigt:
\begin{itemize}
\item \textbf{Zeitstempel abfragen}: Die Funktion \texttt{f:timestamp(?s,?p,?o)} gibt für das gegebene Tripel eines Ereignis-Quadrupels, bestehend aus Subjekt, Prädikat und Objekt, den zugehörigen Zeitstempel des Quadrupels zurück. Das Ergenis dieser Funktion wird später dazu verwendet, zeitliche Abfolgen und Beziehungen von Ereignissen festzustellen. Um die Funktion über das Prefix \texttt{f} ansprechen zu können, ist in der Abfrage folgende Prefix-Definition notwendig:
\begin{lstlisting}
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
\end{lstlisting}
Die Funktion akzeptiert für jeden der drei Parameter neben klassischen Platzhaltern wie \texttt{?var}, auch die direkte Angabe von Werten in Form von URIs sowohl in vollständiger Notation nach dem Schema \texttt{<\emph{uri}>} als auch unter der Nutzung von Prefixen nach dem Schema (\texttt{\emph{prefix}:example}). Auch Literale können als Parameter angegeben werden\footnote{In der aktuell verwendeten Version 0.9.7 der C-SPARQL-Engine gibt es diesbezüglich einen Bug, sodass die Funktion für Tripel, die Literalwerte enhalten, keinen Zeitstempel zurückgibt.}. Ein Beispiel für die Nutzung dieser Funktion kann wie folgt aussehen:
\begin{lstlisting}
f:timestamp(?s,rdf:type,<http://example.org/type/A>)
\end{lstlisting}
\item \textbf{Berechnungsergebnisse speichern}: Um einen Ausdruck in einer lokalen Variable zu speichern wird die Anweisung \texttt{BIND(\emph{ausdruck} AS \emph{?var})} verwendet. Ein Ausdruck kann nahezu alles sein, von einer mathematischen Berechnung wie \texttt{(?i+1)/2} bis hin zu dem Aufruf der \texttt{f:timestamp(?s,?p,?o)}-Funktion. Der folgende Ausdruck demonstriert das Abspeichern der Differenz zwischen den Zeitstempeln zweier Tripel in der Variable \texttt{?difference}:
\begin{lstlisting}
BIND(f:timestamp(?a,?b,?c) - f:timestamp(?x,?y,?z)) AS ?difference)
\end{lstlisting}
\item \textbf{Eventuell vorhandene Tripel selektieren}: Manchmal kann man zur Zeit der For\-mu\-lie\-rung einer Abfrage nicht mit vollständiger Gewissheit eine Aussage da\-\-ber treffen, ob bestimmte Muster von Tripeln in den zur Verfügung stehenden Daten enthalten sind. In diesem Fall hilft der \texttt{OPTIONAL \{ ... \}}-Block weiter: Er erlaubt die Selektion von Tripeln, wobei der Fall, dass keine Tripel selektiert werden konnten keinen negativen Einfluss auf die restliche Abfrage hat. Möchte man beispielsweise das Tripelmuster \texttt{?a :b :c} selektieren und zusätzlich --- falls vorhanden --- die Tripel für das Muster \texttt{?d :e :f}, so kann dies in der \texttt{WHERE}-Klausel wie folgt notiert werden:
\begin{lstlisting}
WHERE {
?a :b :c .
OPTIONAL {
?d :e :f
}
}
\end{lstlisting}
Wurde durch die Abfrage kein Tripel für \texttt{?d :e :f} gefunden, so wird dennoch das Ergebnis für \texttt{?a :b :c} zurückgegeben.
\item \textbf{Variablen auf Werte prüfen}: Hat man den \texttt{OPTIONAL \{ ... \}}-Block verwendet, so stellt sich innerhalb der restlichen Abfrage oft die Frage, ob nun ein Ergebnis vorliegt oder nicht. Über die Abfrage \texttt{ISBOUND(?var)} kann geprüft werden, ob die angegebene Variable --- in diesem Fall \texttt{?var} --- mit einem Wert versehen ist oder nicht.
\item \textbf{Gefundene Tripelmenge einschränken}: Hat man bereits Tripel selektiert und möchte die Ergebnismenge anhand von feineren Kriterien einschränken, so kommt \texttt{FILTER(\emph{bedingung})} zum Einsatz. Die Anweisung nimmt einen Ausdruck als Bedingung entgegen, die für alle gefundenen Tripel ausgewertet wird. Erfüllen gefundene Tripel die Bedingung, so bleiben sie in der Ergebnismenge.
Gegeben seien Tripel nach dem Schema:
\begin{lstlisting}
?person rdf:type :person .
?person :isOfAge ?someAge .
\end{lstlisting}
Um aus diesen Daten nun alle Personen zu selektieren, die über 18 sind, kann die \texttt{FILTER}-Anweisung in der \texttt{WHERE}-Klausel verwendet werden:
\begin{lstlisting}
WHERE {
?person rdf:type :person .
?person :isOfAge ?age .
FILTER(?age >= 18)
}
\end{lstlisting}
Das Ergebnis enthält dann nur noch Personen, die laut ihrer Altersangabe älter als 18 sind.
\item \textbf{Abwesenheit oder Präsenz von Tripeln erkennen}: Unter bestimmten Um\-stän\-den kann es notwendig sein, die Abwesenheit von Tripeln als Kriterium für ein Ergebnis festzulegen. Hiefür kann der Block \texttt{FILTER NOT EXISTS \{ ... \}} eingesetzt werden. Ähnlich wie der \texttt{OPTIONAL}-Block nimmt er Tripelmuster entgegen. Jedoch stellen alle in ihm gefundenen Tripel das Ausschlusskriterium für die zu ihnen bezogene Tripel aus der restlichen Ergebnismenge dar. Ein Beispiel aus \cite{w3c:sparql}[Kapitel 8.1.1] zeigt dies sehr schön. Gegeben seien folgende Daten:
\begin{lstlisting}
:alice rdf:type foaf:Person .
:alice foaf:name "Alice" .
:bob rdf:type foaf:Person .
\end{lstlisting}
Möchte man nun alle Personentripel finden, zu denen \textbf{kein} Name via \texttt{foaf:name} angegeben ist, so führt die folgende Abfrage zum gewünschten Ergebnis und gibt das Personentripel von \texttt{:bob} als Ergebnis zurück:
\begin{lstlisting}
SELECT ?person
WHERE {
?person rdf:type foaf:Person .
FILTER NOT EXISTS { ?person foaf:name ?name }
}
\end{lstlisting}
Natürlich kann man diese Abfrage auch umkehren um alle Personentripel \textbf{mit} einer Namensangabe zu finden. Hierfür kommt anstelle von \texttt{FILTER NOT EXISTS \{ ... \}} nun \texttt{FILTER EXISTS \{ ... \}} zum Einsatz. Das Ergebnis der Abfrage wäre nun das Personentripel von \texttt{:alice}.
\end{itemize}
Um nun in C-SPARQL Ereignismuster formulieren zu können, die den Mustern aus Kapitel~\ref{cpt:cep_intro} entsprechen, müssen zunächst die Operatoren der Ereignisalgebra in die C-SPARQL-Sprache übersetzt werden.
\begin{itemize}
\item \textbf{Ereignistypen} $(A)$: Bevor die Operatoren aus der Ereignisalgebra verwendet werden können, muss zunächst die Selektion von Ereignissen spezifischer Typen gezeigt werden. Wie Listing~\ref{lst:sample_event_rdf_quads} bereits demonstriert hat, wird das Prädikat \texttt{rdf:type} verwendet, um den Ereignistyp einer Ereignisinstanz zu spezifizieren. Diese Angabe kann bei der Selektion von Tripeln verwendet werden, um nur Ereignisinstanzen eines bestimmten Ereignistypen zu erhalten. Platziert man das folgende Muster in der \texttt{WHERE}-Klausel einer C-SPARQL-Abfrage, so erhält man Subjekte von Ereignisinstanzen vom Typ \texttt{<http://example.org/type/A>} in der Variable \texttt{?eventA}:
\begin{lstlisting}
?eventA rdf:type <http://example.org/type/A> .
\end{lstlisting}
\item Der \textbf{Sequenzoperator} $X\ \rightarrow\ Y$: Um die zeitliche Abfolge von Ereignissen mit C-SPARQL zu formulieren, wird die \texttt{f:timestamp(?s,?p,?o)}-Funktion mit der \texttt{FILTER}-Anweisung kombiniert. Hat man bereits zwei Ereignisse --- je eines vom Typ X beziehungsweise Y --- selektiert, so kann man diese Anhand ihrer Zeitstempel filtern, sodass nur in der Ergebnismenge nur die Ereignisinstanzen übrig bleiben, für die gilt: Ereignis vom Typ X kam vor dem Ereignis von Typ Y.
Innerhalb der \texttt{WHERE}-Klausel sieht dies wie folgt aus:
\begin{lstlisting}
?eventX rdf:type <http://example.org/type/X> .
?eventY rdf:type <http://example.org/type/Y> .
FILTER(f:timestamp(?eventX,rdf:type,<http://example.org/type/X>) < f:timestamp(?eventY,rdf:type,<http://example.org/type/Y>))
\end{lstlisting}
\item Die \textbf{boolschen Operatoren} $\wedge$ und $\vee$: Mit dem Operator $\wedge$ kann über Ausdrücke wie $(A \wedge B)$ definiert werden, dass sowohl ein Ereignis vom Typ A als auch ein Ereignis vom Typ B im Muster vorkommen müssen. Um dies innerhalb von C-SPARQL auszudrücken, müssen innerhalb der \texttt{WHERE}-Klausel lediglich die Tripel selektiert werden, die zu den Ereignistypen korrespondieren:
\begin{lstlisting}
WHERE {
?eventA rdf:type <http://example.org/type/A> .
?eventB rdf:type <http://example.org/type/B> .
}
\end{lstlisting}
Sollte eines der beiden Tripel nicht in der zu verarbeitenden Datenmenge enthalten sein, so gibt die gesamte C-SPARQL-Abfrage kein Ergebnis zurück. Somit ist gewährleistet, dass diese \texttt{WHERE}-Klausel dem Ereignismuster $(A \wedge B)$ entspricht.
Für den Operator $\vee$, mit dem eine entweder-oder-Beziehung ausgedrückt werden kann, ist etwas mehr Arbeit erforderlich. Nimmt man den Ausdruck $(A \vee B)$, so bedeutet dieser, dass \emph{entweder} ein Ereignis vom Typ A \emph{oder} ein Ereignis vom Typ B vorkommt, jedoch nicht beide zugleich. Innerhalb von C-SPARQL bedeutet dies, dass für die Selektion der Tripel für die Ereignistypen A und B zwei \emph{separate} \texttt{OPTIONAL}-Blöcke verwendet werden müssen. Auf Basis der selektierten Tripel wird dann eine \texttt{FILTER}-Anweisung gebaut, die mit Hilfe von \texttt{BOUND()} alle Tripel aus der Ergebnismenge ausschließt, bei denen beide oder keiner von beiden Ereignistypen vorhanden ist:
\begin{lstlisting}
WHERE {
OPTIONAL {
?a rdf:type <http://example.org/type/A> .
}
OPTIONAL {
?b rdf:type <http://example.org/type/B> .
}
FILTER( (BOUND(?a) || BOUND(?b))
&& !(BOUND(?a) && BOUND(?b)) )
}
\end{lstlisting}
Das Ergebnis sind ausschließlich Tripel, in denen \emph{entweder} ein Ereignis vom Typ A \emph{oder} ein Ereignis vom Typ B vorkommt.
\item Der \textbf{Negationsoperator} ($\neg A$): In C-SPARQL unterscheidet sich die Abbildung des Negationsoperators je nach Ereignismuster, in dem er verwendet wird. Um ein Muster vom Schema $(\neg A)$ abbilden zu können, kann eine Kombination aus \texttt{OPTIONAL}-Block und \texttt{FILTER}-Anweisung verwendet werden. Neben der Selektion aller verfügbaren Ereignistripel wird im \texttt{OPTIONAL
}-Block ein Tripel für den Ereignistypen A selektiert. Die auf den Block folgende \texttt{FILTER}-Anweisung filtert dann mit Hilfe von \texttt{BOUND()} nach dem Kriterium, ob das im \texttt{OPTIONAL}-Block selektierte Ereignistripel gefunden wurde oder nicht. Die entsprechende \texttt{WHERE}-Klausel dieser Abfrage sieht dann wie folgt aus:
\begin{lstlisting}
WHERE {
?anyEvent rdf:type ?anyEventType .
OPTIONAL {
?eventA rdf:type <http://example.org/type/A> .
}
FILTER(!BOUND(?eventA))
}
\end{lstlisting}
Ein komplexeres Beispiel, in dem ein bestimmter Ereignistyp lediglich nicht zwischen zwei anderen Ereignistypen auftauchen darf, zeigt das folgende Ereignismuster:
\[(A)\ \rightarrow\ (\neg B)\ \rightarrow\ (C)\]
Dieses Muster verbietet die Vorkommnis von Ereignistyp B lediglich dann, wenn er zeitlich zwischen Ereignissen von Typ A und C liegt. Die in Listing~\ref{lst:example_csparql_query} gezeigte Beispielabfrage löst genau dieses Problem mit Hilfe eines \texttt{FILTER NOT EXISTS}-Blocks. Hierfür werden zunächst existierende Ereignisse vom Typ A und C selektiert und mit einer \texttt{FILTER}-Anweisung die Ergebnisse verworfen, in denen die Ereignisse vom Typen C nicht vor denen vom Typ A vorkommen. Dann wird via \texttt{FILTER NOT EXISTS} ein Ereignistripel vom Typ B gesucht, welches zeitlich \emph{zwischen} den gefundenen Ereignissen vom Typ A und C liegt. Falls innerhalb des Blockes ein passendes Ereignistripel gefunden wurde, so gehören die zugehörigen Ereignistripel vom Typ A und C nicht zur Ergebnismenge. Damit bleiben nur noch Ereignisse übrig, die das oben genannte Ereignismuster erfüllen.
\end{itemize}
\paragraph{Aggregation von Ereignissen}
Ergeben sich für Ereignismuster über ein Sliding Window eine größere Menge an Ergebnissen, so kann es sinnvoll sein, diese Anhand von bestimmten Kriterien in einzelne Gruppen zusammenzufassen und mittels Aggregationsfunktionen diese Gruppen zusammenzufassen. Um dies zu tun wurde in Kapitel~\ref{cpt:cep_aggregate_events} das Konstrukt \texttt{AGGREGATE} eingeführt.
Als passendes Gegenstück gibt es C-SPARQL das Konstrukt \texttt{GROUP BY (\emph{?var})}, wobei \texttt{?var} eine Variable aus der \texttt{WHERE}-Klausel ist, nach der gruppiert werden soll. Wird eine solche Gruppierung vorgenommen, so können über die daraus resultierenden Gruppen die folgenden Aggregationsfunktionen zur Zusammenfassung der Ergebnisgruppen verwendet werden\cite{barbieri:querying}:
\begin{itemize}
\item \texttt{COUNT(\emph{?var})}: Zählt die Anzahl Tupel pro Gruppe
\item \texttt{SUM(\emph{?var})}: Bildet Summe der Variable über jede Gruppe
\item \texttt{AVG(\emph{?var})}: Bildet Mittelwert der Variable über jede Gruppe
\item \texttt{MIN(\emph{?var})}: Ermittelt das Minimum der gegebenen Variable pro Gruppe
\item \texttt{MAX(\emph{?var})}: Ermittelt das Maximum der gegebenen Variable pro Gruppe
\end{itemize}
Ähnlich wie bei SQL gibt es auch in C-SPARQL die Anweisung \texttt{HAVING}, mit dem Kriterien für die durch \texttt{GROUP BY} gebildeten Gruppen festgelegt werden können. Ist die in \texttt{HAVING} angegebene Bedingung für eine Gruppe nicht erfüllt, so wird sie aus der Ergebnismenge entfernt.
Nachdem nun die grundlegenden Werkzeuge zur Aggregation von Daten in C-SPARQL erläutert wurden, sollen diese in mit einem Beispiel demonstriert werden: Gegeben seien Statusmeldeereignisse von PKW, die zur weiteren Auswertung aggregiert werden sollen. Hierbei sollen für jeden einzelnen PKW, dessen durchschnittliche Motordrehzahl über 3000 Umdrehungen pro Minute liegt, die minimale beziehungsweise maximale Motordrehzahl ermittelt werden.
Abstrakt formuliert ergibt sich hierbei folgende CEP-Regel:
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION ($CarStatusEvent\ AS\ statusEvent$)
$\wedge$ AGGREGATE(statusEvent, "motorRPM", {statusEvent.relatedCar}, MIN) AS minRPM
$\wedge$ AGGREGATE(statusEvent, "motorRPM", {statusEvent.relatedCar}, MAX) AS maxRPM
$\wedge$ AGGREGATE(statusEvent, "motorRPM", {statusEvent.relatedCar}, AVG) > 3000
ACTION
... auszulösende Aktionen ...
\end{lstlisting}
Um dies in C-SPARQL umzusetzen, müssen die Ereignistupel nach den PKW, auf die sie sich beziehen mit \texttt{GROUP BY} gruppiert werden. Dann können die gewünschten Werte in der \texttt{SELECT}-Klausel unter Nutzung der Funktionen \texttt{MIN()} und \texttt{MAX()} selektiert werden. Zuletzt kann das Kriterium, dass die durchschnittliche Motordrehzahl 3000 Umdrehungen pro Minute überschreitet mit einer \texttt{HAVING}-Anweisung umgesetzt werden. Die folgende C-SPARQL-Abfrage demonstriert dies:
\begin{lstlisting}[label={lst:sample_csparql_aggregation_query},caption={Ermittlung minimaler und maximaler Motordrehzahl pro PKW}]
SELECT (MIN(?carRPM) AS ?minRPM) (MAX(?carRPM) AS ?maxRPM)
FROM STREAM ...
WHERE {
?carStatusEvent rdf:type carOnt:CarStatusEvent .
?carStatusEvent carOnt:relatedCar ?car .
?carStatusEvent carOnt:motorRPM ?carRPM .
}
GROUP BY ?car
HAVING AVG(?carRPM) > 3000
\end{lstlisting}
Somit stellt die Aggregation von Ereignisdaten kein Problem dar und kann regulär in CEP-Regeln verwendet werden.
\section{Auslösen von Aktionen}\label{cpt:register_stream}
Ein wichtiges Werkzeug bei der Verarbeitung von Ereignisdatenströmen ist das Auslösen von Aktionen. Hier gibt es im allgemeinen zwei unterschiedliche Wege, um Aktionen auslösen zu können. Diese eignen sich je nach konkretem Vorhaben unterschiedlich gut.
\paragraph{Auslösen von Ereignissen}
Das Auslösen von Ereignissen innerhalb der Ereignisverarbeitung kann genutzt werden, um eine mehrstufige Auswertung mit CEP-Regeln zu erhalten. Hierbei werden die Ergebnisse aus der Verarbeitung durch CEP-Regel genutzt, um den Typen und die Attributwerte des neuen Ereignisses zu bestimmen.
In C-SPARQL können bestimmte Abfragen, wie etwa \texttt{CONSTRUCT}-Queries als Datenströme registriert werden, um somit ihre Ergebnisse in weiteren CEP-Regeln verarbeiten zu können. Hierzu wird nach dem Schlüsselwort \texttt{REGISTER}, wie die Grammatik aus Listing~\ref{lst:register_query_csparql} beschreibt, das Schlüsselwort \texttt{STREAM} angegeben\footnote{Zusätzlich ist es notwendig, den Query nach der Registrierung von einem \texttt{RDFStreamFormatter} beobachten zu lassen, den man separat als Stream an der Engine registrieren muss. Mehr dazu in Kapitel~\ref{cpt:csparql_in_practice}}.
Um dies zu zeigen, sei in Listing~\ref{lst:abstract_cep_rule_five} eine abstrakte CEP-Regel gegeben, die Ereignisse vom Typ \texttt{Car\allowbreak Status\allowbreak Event} nach den im Attribut \texttt{relatedCar} angegebenen PKW gruppiert, für jeden PKW dessen durchschnittliche Geschwindigkeit ermittelt und für jedes Ergebnis ein neues Ereignis vom Typ \texttt{AverageSpeedEvent} erzeugt, welches die Durchschnittsgeschwindigkeit enthalten soll.
\begin{lstlisting}[mathescape=true,label={lst:abstract_cep_rule_five},caption={CEP-Regel erzeugt neue Ereignisse}]
CONDITION ($(CarStatusEvent\ AS\ statusEvent)$)[WindowSize:15min,StepSize:5m]
$\wedge$ AGGREGATE(statusEvent, "speed", {statusEvent.relatedCar}, AVG) AS avgSpeed
ACTION
new AverageSpeedEvent(relatedCar=statusEvent.relatedCar, averageSpeed=avgSpeed)
\end{lstlisting}
Diese CEP-Regel lässt sich mit C-SPARQL umsetzen, indem eine entsprechende Abfrage mit dem Schlüsselwort \texttt{CONSTRUCT} formuliert wird, welche die Durchschnittsgeschwindigkeit der PKW ermittelt und daraus neue Ereignisinstanzen vom Typ \texttt{Average\allowbreak Speed\allowbreak Event} generiert. Listing~\ref{lst:csparql_construct_query} zeigt diese C-SPARQL-Abfrage:
\begin{lstlisting}[label={lst:csparql_construct_query},caption={Konstruktion eines Ereignisstromes mit C-SPARQL}]
REGISTER STREAM getAverageSpeedByCar AS
CONSTRUCT {
[] rdf:type car:AverageSpeedEvent
; car:relatedCar ?car
; car:averageSpeed ?avgSpeed .
}
FROM STREAM <...> [RANGE 15m STEP 1m]
WHERE {
{
SELECT ?car (AVG(?speed) AS ?avgSpeed)
WHERE {
?e rdf:type car:CarStatusEvent .
?e car:relatedCar ?car .
?e car:speed ?speed .
}
GROUP BY (?car)
}
}
\end{lstlisting}
Da C-SPARQL die gleichzeitige Nutzung von \texttt{GROUP BY} mit \texttt{CONSTRUCT} erlaubt, nutzt die Abfrage aus Listing~\ref{lst:csparql_construct_query} einen in geschweiften Klammern eingefassten Subquery, um die Aggregation vornehmen zu können. Um für die innerhalb von \texttt{CONSTRUCT} neu zu erzeugten Ereignisinstanzen ein Subjekt zu erhalten, wird mit \texttt{[]} ein sogenannter \emph{Blank Node} verwendet. Innerhalb der \texttt{CONSTRUCT}-Anweisung wird nur das letzte Tripel mit einem Punkt beendet; die letzten beiden Tripel zeigen durch die Verwendung eines Semikolons an, dass sie das selbe Subjekt wie das erste Tripel für ihre Aussage verwenden. Somit haben alle drei der konstruierten Tripel das selbe Blank Node als Subjekt. Nach Auswertung des Queries erhält jeder Blank Node von der Engine eine Kennung, anhand welcher er identifiziert werden kann.
\paragraph{Ausführen von Code und Anstoßen externer Dienste}
Die C-SPARQL-Engine bietet auch Möglichkeiten zum Ausführen von eigenem Code und somit auch zum Anstoßen von externen Diensten. Allerdings bietet sie dafür keine Möglichkeiten innerhalb der C-SPARQL-Sprache selbst, sondern erfordert das Anbringen von einem \texttt{Observer} an den durch die Registrierung eines Queries an der Engine entstandenen \texttt{Csparql\allowbreak Query\allowbreak Result\allowbreak Proxy}. Dieser wird jedes Mal benachrichtigt, wenn für den registrierten Query ein Ergebnis vorliegt. Dabei wird die durch das \texttt{Observer}-Interface implementierte Methode \texttt{update()} aufgerufen, welcher der \texttt{CsparqlQueryResultProxy} des Queries sowie die \texttt{RDFTable} mit den Ergebnissen übergeben wird. Innerhalb der Methode \texttt{update()} kann dann beliebiger Java-Code hinterlegt werden, der mit den Ergebnisdaten arbeitet oder einen externen Dienst anstößt.
Somit kann von der CEP-Regel in Listing~\ref{lst:abstract_cep_rule_six} nur der \texttt{CONDITION}-Teil in C-SPARQL umgesetzt werden (Listing~\ref{lst:csparql_speedavg_greater_140}), da für den \texttt{ACTION}-Teil zusätzlicher Java-Code benötigt wird:
\begin{lstlisting}[mathescape=true,label={lst:abstract_cep_rule_six},caption={CEP-Regel stößt externen Dienst an}]
CONDITION ($(CarStatusEvent\ AS\ statusEvent)$)[WindowSize:15min,StepSize:5m]
$\wedge$ AGGREGATE(statusEvent, "speed", {statusEvent.relatedCar}, AVG) AS avgSpeed
$\wedge$ avgSpeed > 140
ACTION
callNotifyCustomerIsSpeedingService(
relatedCar=statusEvent.relatedCar,
averageSpeed=avgSpeed)
\end{lstlisting}
\begin{lstlisting}[label={lst:csparql_speedavg_greater_140},caption={Fahrzeuge mit Durchschnittsgeschwindigkeit $>$ 140km/h}]
REGISTER QUERY getSpeedingCars AS
SELECT ?car (AVG(?speed) AS ?avgSpeed)
FROM STREAM <...> [RANGE 15m STEP 1m]
WHERE {
?e rdf:type car:CarStatusEvent .
?e car:relatedCar ?car .
?e car:speed ?speed .
}
GROUP BY (?car)
\end{lstlisting}
\section{Einbindung von Domänenwissen}\label{cpt:integrating_knowledge}
Zur Einbindung von lokalem Domänenwissen in einem Query bietet C-SPARQL die Möglichkeit, auf Daten aus lokal angelegten Graphen zuzugreifen. Bevor das lokale Domänenwissen in C-SPARQL-Queries zur Verfügung steht, muss es zunächst in einen Graphen geladen werden. Wie dies geschieht, wird in Kapitel~\ref{cpt:csparql_domain_knowledge} beschrieben. Sind nun Daten in dem Graphen mit der URI \path{http://example.org/carSimKnowledgeGraph} hinterlegt worden, so ist der Zugriff aus einem C-SPARQL-Query heraus leicht: Es ist lediglich notwendig, den Graphen mit einer zusätzlichen \texttt{FROM}-Klausel als Datenquelle anzugeben:
\begin{lstlisting}
FROM <http://example.org/carSimKnowledgeGraph>
\end{lstlisting}
Danach stehen die Daten aus dem Graphen im Kontext der Abfrage zur frei zur Ver\-\-gung. Der folgende C-SPARQL-Query nutzt Daten aus dem lokalen Domänenwissen, um für zu schnell fahrende PKW den aktuellen Fahrer zu ermitteln:
\begin{lstlisting}[label={lst:csparql_lookup_driver},caption={Zugriff auf lokales Domänenwissen aus Graph}]
REGISTER QUERY getSpeedingCars AS
SELECT ?driverName ?car (AVG(?speed) AS ?avgSpeed)
FROM STREAM <...> [RANGE 15m STEP 1m]
FROM <http://example.org/carSimKnowledgeGraph>
WHERE {
?e rdf:type car:CarStatusEvent .
?e car:relatedCar ?car .
?e car:speed ?speed .
?driver car:isDriving ?car .
?driver car:hasName ?driverName .
}
GROUP BY (?car)
\end{lstlisting}
Die Verknüpfung von Ereignisdaten mit lokalem Domänenwissen ist bei der Nutzung der C-SPARQL-Engine direkt innerhalb von CEP-Regeln möglich und erleichtert somit deutlich die Formulierung von Regeln.
\section{Reasoning auf RDF-Datenströmen}
Die C-SPARQL-Engine unterstützt die automatische Anreicherung von RDF-Da\-ten\-strö\-men und Abfrageergebnissen durch implizites Wissen, welches durch ein gegebenes RDFS-Vokabular abgeleitet werden konnte. Somit ist die Engine in der Lage, auf RDF-Datenströmen Reasoning durchzuführen, um diese um implizites Wissen anzureichern. Der praktische Nutzen daraus ist höhere Flexibilität bei der Formulierung von C-SPARQL-Queries; insbesondere die automatische Anreicherung von Ereignisinstanztypen aus Hierarchien von Objektklassen kann Generalisierungen von CEP-Regeln deutlich vereinfachen. Hierzu ein Beispiel:
\todo{...}
Gegeben sei folgende TBox mit Vokabular im RDF-Schema:
\begin{lstlisting}[label={reasoning_tbox},caption={TBox mit Klassen und Attributdefinitionen aus RDFS}]
:CarWearEvent rdf:type rdfs:Class .
:CarBrakeWearEvent rdf:type rdfs:Class .
:CarBrakeWearEvent rdfs:subClassOf :CarWearEvent .
\end{lstlisting}
Diese wird nun in einem Reasoning-Prozess mit der folgenden ABox kombiniert:
\begin{lstlisting}[label={reasoning_abox},caption={ABox mit Fakten, die sich auf die TBox aus Listing~\ref{reasoning_tbox} beziehen}]
:SomeThing rdf:type :Thing .
\end{lstlisting}
Das Ergebnis hieraus sind folgende Tripel, die \emph{zusätzlich} zu den Tripeln aus der ABox nun zur Abfrage durch C-SPARQL-Queries zur Verfügung stehen:
\begin{lstlisting}[label={reasoning_result},caption={Durch Reasoning ermitteltes, implizites Wissen}]
TODO: ERGEBNIS
\end{lstlisting}
Die technischen Details von Reasoning in der C-SPARQL-Engine werden in Kapitel~\ref{cpt:csparql_reasoning} beleuchtet.
\chapter{Umsetzung des Beispielszenarios}\label{cpt:csparql_in_practice}
Nachdem die Umsetzung von CEP-Regeln in C-SPARQL im vorherigen Kapitel erläutert wurde, soll nun das in Kapitel~\ref{cpt:scenario} angerissene Beispielszenario der Autoverleihgesellschaft umgesetzt werden. Eine für diesen Zweck entwickelte, einfach gehaltene Software simuliert die Bestandteile des Beispielszenarios und stellt als Ausgangspunkt zur Verarbeitung zwei Ereignisdatenströme bereit. Zusätzlich dazu sind eine lokale Wissensbasis (ABox) und ein grundlegendes Vokabular (TBox) gegeben, wobei die TBox Definitionen für die in der ABox und in den Ereignisdatenströmen verwendeten Objektklassen und Attributen enthält. Mit diesen Resourcen soll die Umsetzung der Anforderungen des Beispielszenarios Schritt für Schritt durchgeführt werden.
Im Folgenden werden zunächst die beiden zu verarbeitenden RDF-Ereignisdatenströme vorgestellt:
\paragraph{PKW-Ereignisstrom}
Dieser Ereignisdatenstrom übermittelt alle Ereignisse, die von den PKW aus dem Fuhrpark der Verleihgesellschaft ausgehen. Jedes einzelne Ereignis bezieht sich dabei auf einen konkreten PKW. Die folgenden Ereignistypen werden durch den Datenstrom übermittelt:
\begin{itemize}
\item \textbf{\texttt{Car\allowbreak Status\allowbreak Event}}: Wird von jedem PKW in regelmäßigen Abständen ausgelöst und übermittelt ein Paket von Statuswerten. Neben dem Attribut \texttt{relatedCar}, welches auf den jeweiligen PKW verweist, werden die Attribute \texttt{motorOn}, \texttt{motorRPM}, \texttt{speed}, \texttt{handbrakeEngaged}, \texttt{locked} und \texttt{tirePressure\{1-4\}} für die Beschreibung der Statuswerte verwendet. Da dieses der Ereignistyp mit den meisten Attributen ist, wird im Folgenden ein Beispiel für eine solche Ereignisinstanz gezeigt:
\begin{lstlisting}
:event rdf:type car:CarStatusEvent .
:event car:relatedCar :someCar .
:event car:motorOn true^^xsd:boolean .
:event car:motorRPM 1821^^xsd:integer .
:event car:speed 25^^xsd:integer .
:event car:handbrakeEngaged false^^xsd:boolean .
:event car:locked false^^xsd:boolean .
:event car:tirePressure1 29^^xsd:integer .
:event car:tirePressure2 28^^xsd:integer .
:event car:tirePressure3 30^^xsd:integer .
:event car:tirePressure4 29^^xsd:integer .
\end{lstlisting}
\item \textbf{\texttt{CarLockEvent} und \texttt{CarUnlockEvent}}: Sobald ein Fahrer den PKW auf- oder abschließt, wird jeweils ein \texttt{CarLockEvent} beziehungsweise \texttt{CarUnlockEvent} ausgelöst. Dieses enthält lediglich eine Angabe des betroffenen PKW über das Attribut \texttt{relatedCar}.
\item \textbf{\texttt{CarHandbrakeEngageEvent} und \texttt{Car\allowbreak Handbrake\allowbreak Release\allowbreak Event}}: Wird die Handbremse angezogen oder gelöst, so löst der PKW ein entsprechendes Ereignis vom Typ \texttt{CarHandbrakeEngageEvent} oder \texttt{Car\allowbreak Handbrake\allowbreak Release\allowbreak Event} aus. Diese enthalten nur die Angabe des PKW über das Attribut \texttt{relatedCar}.
\item \textbf{\texttt{CarCheckEngineEvent}}: Sollte die Bordelektronik eines PKW eine Fehlermeldung auslösen, so wird diese mit einem \texttt{CarCheckEngineEvent} angezeigt. Hierbei wird der betroffene PKW über \texttt{relatedCar} angegeben.
\item \textbf{\texttt{CarAirbagTriggeredEvent}}: Wurde an einem PKW der Airbag ausgelöst, so wird ein Ereignis dieses Typen ausgelöst, um auf diesen Umstand aufmerksam zu machen. Auch hier wird lediglich der PKW über \texttt{relatedCar} angegeben.
\end{itemize}
\paragraph{Kundenereignisstrom}
Die Ereignisse in diesem Datenstrom beziehen sich ausschließlich auf Interaktionen von Kunden mit den PKW. Somit werden durh diesen Ereignisdatenstrom nur Ereignisse dieser zwei Typen übertragen:
\begin{itemize}
\item \texttt{CarTakenEvent}: Wird ausgelöst, wenn ein Kunde einen PKW ausleiht. Ein solches Ereignis verweist über \texttt{relatedCar} auf den geliehenen PKW und über \texttt{related\allowbreak User} auf den entsprechenden Kunden.
\item \texttt{CarReturnedEvent}: Analog zum Ereignistypen \texttt{CarTakenEvent} wird dieses Ereignis ausgelöst, wenn ein Kunde sein geliehenes Fahrzeug wieder zurückgibt. Ebenso wie das \texttt{CarTakenEvent} wird über \texttt{relatedCar} auf den entsprechenden PKW verwiesen und über \texttt{relatedUser} auf den Kunden.
\end{itemize}
\section{Lokales Domänenwissen}
Neben den beiden Ereignisdatenströmen existiert eine lokale Datenbasis, in der neben einer Kartei mit Kundendaten auch Informationen über die vorhandenen PKW vorgehalten werden. Diese beinhalten auch Angaben über das Modell jedes einzelnen PKW, sowie technische Daten der Modelle, wie zum Beispiel der empfohlene Reifendruck oder die maximal zulässige Motordrehzahl im Betrieb.
Es folgt ein gekürzter Auszug aus der ABox der lokalen Wissensbasis:
\begin{lstlisting}[label={lst:simulation_abox_excerpt},caption={Auszug aus der ABox der Simulation}]
:Driver#0 rdf:type car:Driver
:Driver#0 car:hasName "Max Mustermann"^^xsd:string .
:Driver#0 car:hasPhoneNumber "+49 111 123456789"^^xsd:string .
:Driver#0 car:hasDriverLicense "B"^^xsd:string .
:Car#2 rdf:type car:Car .
:Car#2 car:isCarModel :CarModel#1 .
:CarModel#1 rdf:type car:CarModel .
:CarModel#1 car:minimumMotorRPM 800^^xsd:integer .
:CarModel#1 car:maximumMotorRPM 1900^^xsd:integer .
:CarModel#1 car:minimumTirePressure 35^^xsd:integer .
:CarModel#1 car:maximumTirePressure 38^^xsd:integer .
:CarModel#1 car:requiresDriverLicense "B"^^xsd:string .
\end{lstlisting}
Für die Umsetzung dieses Beispielszenarios befindet sich das lokale Domänenwissen in dem Graphen \path{http://example.org/carSim/localDomainKnowledge}.
\section{Umsetzung der Anforderungen}
Um diese Ereignisdatenströme nun zu Verarbeiten, werden die Anforderungen des Szenarios aus Kapitel~\ref{cpt:scenario} zunächst genauer betrachtet und CEP-Regeln für sie formuliert.
\subsection{Erkennung von Verschleiß am Fahrzeug} Um durch unsachgemäße Nutzung bedingten Verschleiß an den PKW feststellen zu können, sind Ereignisse vom Typ \texttt{Car\allowbreak Status\allowbreak Event} ein guter Einstiegspunkt. So lassen sich gleich mehrere Arten von Verschleiß hieraus ermitteln:
\begin{itemize}
\item \textbf{Verschleiß des Motors durch starkes Beschleunigen:}
Hierfür genügt es, zwei direkt aufeinanderfolgende \texttt{Car\allowbreak Status\allowbreak Event}s zu betrachten, die sich auf den selben PKW beziehen. Ermittelt man zwischen den beiden Ereignissen die Geschwin\-dig\-keits\-dif\-fe\-renz und prüft, ob diese größer als ein gegebener Schwellwert (beispielsweise 25km/h) ist. Trifft dies zu, so wurde eine starke Beschleunigung erkannt.
Die hieraus resultierende, abstrakte CEP-Regel sieht wie folgt aus:
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION ($CarStatusEvent\ AS\ c1 \rightarrow\ CarStatusEvent\ AS\ c2$)
$\wedge$ c1.relatedCar = c2.relatedCar
$\wedge$ c2.speed > c1.speed
$\wedge$ c2.speed-c1.speed AS deltaSpeed
$\wedge$ deltaSpeed > 25
ACTION
new CarStrongAccelerationEvent(relatedCar=c1.relatedCar, deltaSpeed=deltaSpeed)
\end{lstlisting}
Übersetzt man dies in einen C-SPARQL-Query, so erhält man folgende Abfrage:
\begin{lstlisting}[label={lst:scenario_strong_acceleration},caption={Erkennung von starker Beschleunigung mit C-SPARQL}]
REGISTER STREAM getStronglyAcceleratingCars AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
CONSTRUCT {
[] rdf:type car:CarStrongAcceleratingEvent
; car:relatedCar ?car
; car:deltaSpeed ?deltaSpeed .
}
FROM STREAM <http://example.org/carSim/stream/carStream> [RANGE 5s STEP 1s]
WHERE {
?e1 rdf:type car:CarStatusEvent .
?e1 car:relatedCar ?car .
?e1 car:speed ?speed1 .
?e2 rdf:type car:CarStatusEvent .
?e2 car:relatedCar ?car .
?e2 car:speed ?speed2 .
FILTER NOT EXISTS {
?ex rdf:type car:CarStatusEvent .
FILTER(f:timestamp(?e1,rdf:type,car:CarStatusEvent) < f:timestamp(?ex,rdf:type,car:CarStatusEvent))
FILTER(f:timestamp(?ex,rdf:type,car:CarStatusEvent) < f:timestamp(?e2,rdf:type,car:CarStatusEvent))
}
BIND(?speed2 - ?speed1 AS ?deltaSpeed)
FILTER(f:timestamp(?e1,rdf:type,car:CarStatusEvent) < f:timestamp(?e2,rdf:type,car:CarStatusEvent))
FILTER(?deltaSpeed > 25)
}
\end{lstlisting}
Diese Abfrage wird nun als Ereignisdatenstrom mit der URI \path{http://example.org/carSim/stream/getStronglyAcceleratingCars} an der Engine registriert, und kann dann innerhalb von weiteren C-SPARQL-Queries verwendet werden.
\item \textbf{Verschleiß der Bremsen durch sehr starke Bremsmanöver:}
Die Umsetzung hier ist sehr ähnlich zu dem Query aus Listing~\ref{lst:scenario_strong_acceleration}, da der einzige Unterschied die Beschleunigungsrichtung ist. Somit ist für die Erkennung lediglich die Vertauschung der beiden Geschwindigkeitsparameter bei der Differenzbildung notwendig. Am Ende der Auswertung wird dann ein Ereignis vom Typ \texttt{CarStrongBrakeEvent} gefeuert.
\item \textbf{Verschleiß des Motors durch Fahrt mit überhöhter Motordrehzahl:} Um die andauernd überhöhte Motordrehzahl eines PKW zu erkennen, müssen mehrere \texttt{Car\allowbreak Status\allowbreak Event}s für einen einzelnen PKW in einem Zeitfenster betrachtet werden und die durchschnittliche Motordrehzahl durch den Einsatz von Aggregationsfunktionen ermittelt werden. Dieser ermittelte Mittelwert muss nun mit den im Domänenwissen hinterlegten Daten für das Modell des PKW abgeglichen werden, um festzustellen, ob er sich noch in dem für den Betrieb zulässigen Bereich befindet.
Die folgende, abstrakte CEP-Regel stellt diesen Vorgang dar:
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION ($(CarStatusEvent\ AS\ statusEvent)$)[WindowSize:15s,StepSize:5s]
$\wedge$ AGGREGATE(statusEvent, "motorRPM", {statusEvent.relatedCar}, AVG) AS avgMotorRPM
$\wedge$ lookup("maxMotorRPM", statusEvent.relatedCar) AS maxMotorRPM
$\wedge$ avgMotorRPM > maxMotorRPM
ACTION
new CarEngineWearEvent(relatedCar=c.relatedCar, avgMotorRPM=avgMotorRPM, maxMotorRPM=maxMotorRPM)
\end{lstlisting}
Übersetzt man diese CEP-Regel in einen C-SPARQL-Query, so erhält man folgende Abfrage:
\begin{lstlisting}[label={lst:scenario_get_engine_wear},caption={Ermittlung überhöhter Motordrehzahl im Betrieb}]
REGISTER STREAM getEngineWear AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
CONSTRUCT {
[] rdf:type car:CarEngineWearEvent
; car:relatedCar ?car
; car:avgMotorRPM ?avgMotorRPM
; car:maxMotorRPM ?maxMotorRPM .
}
FROM STREAM <http://example.org/carSim/stream/carStream> [RANGE 15s STEP 5s]
FROM <http://example.org/carSim/localDomainKnowledge>
WHERE {
{
SELECT ?car (AVG(?motorRPM) AS ?avgMotorRPM)
WHERE {
?e rdf:type car:CarStatusEvent .
?e car:relatedCar ?car .
?e car:motorRPM ?motorRPM .
}
GROUP BY (?car)
}
?car car:isCarModel ?carModel .
?carModel car:maximumMotorRPM ?maxMotorRPM .
FILTER(?avgMotorRPM > ?maxMotorRPM)
}
\end{lstlisting}
Auch diese Abfrage muss später in der Engine als Datenstrom registriert werden, um innerhalb von weiteren Abfragen auf die Ergebnisse dieses Queries zugreifen zu können. Hierfür wird die URI \path{http://example.org/carSim/stream/getEngineWear} verwendet.
\item \textbf{Verschleiß der Handbremse durch Fahren mit angezogener Handbremse:}
Da das \texttt{Car\allowbreak Status\allowbreak Event} Informationen zur Momentangeschwindigkeit eines PKW und zum Status dessen Handbremse liefert, ist es völlig ausreichend, für alle Statusereignisse ein neues Verschleißereignis auszulösen, in denen die Geschwindigkeit bei angezogener Handbremse größer als null ist. Abstrakt formuliert sieht dies so aus:
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION ($(CarStatusEvent\ AS\ statusEvent)$)[WindowSize:3s,StepSize:1s]
$\wedge$ statusEvent.handbrakeEngaged = true
$\wedge$ statusEvent.speed > 0
ACTION
new CarHandbrakeWearEvent(relatedCar=statusEvent.relatedCar)
\end{lstlisting}
In einem C-SPARQL-Query sieht dies dann wie folgt aus:
\begin{lstlisting}[label={},caption={Erkennung von Verschleiß der Handbremse}]
REGISTER STREAM getHandbrakeWear AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
CONSTRUCT {
[] rdf:type car:CarHandbrakeWearEvent
; car:relatedCar ?car .
}
FROM STREAM <http://example.org/carSim/stream/carStream> [RANGE 3s STEP 1s]
WHERE {
?e rdf:type car:CarStatusEvent .
?e car:relatedCar ?car .
?e car:handbrakeEngaged ?handbrakeEngaged .
?e car:speed ?speed .
FILTER(?speed > 0)
FILTER(?handbrakeEngaged = true)
}
\end{lstlisting}
Registriert man diese Abfrage ebenfalls als Datenstrom an der Engine, so kann man auch auf die Ergebnisse dieser Abfrage aus anderen C-SPARQL-Queries heraus zugreifen, um diese weiter zu verarbeiten. Für diesen Datenstrom wird die URI \path{http://example.org/carSim/stream/getHandbrakeWear} verwendet.
\item \textbf{Verschleiß der Reifen durch Fahren mit zu niedrigem Reifendruck:} Das Verfahren hierfür ist vergleichsweise ähnlich zu der Erkennung von Verschleiß an der Handbremse. Allerdings muss hier zusätzlich auf das lokale Domänenwissen zugegriffen werden, um den aktuellen Druck der Reifen mit dem empfohlenen Reifendruckbereich zu vergleichen. Liegt der Druck eines Reifen nicht im empfohlenen Bereich, so soll ein \texttt{CarTireWearEvent} ausgelöst werden.
Als CEP-Regel sieht dies so aus:
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION ($(CarStatusEvent\ AS\ statusEvent)$)[WindowSize:15s,StepSize:5s]
$\wedge$ statusEvent.speed > 0
$\wedge$ lookup("minimumTirePressure", statusEvent.relatedCar) AS minTirePressure
$\wedge$ (
(statusEvent.tirePressure1 < minTirePressure)
$\vee$ (statusEvent.tirePressure2 < minTirePressure)
$\vee$ (statusEvent.tirePressure3 < minTirePressure)
$\vee$ (statusEvent.tirePressure4 < minTirePressure)
)
ACTION
new CarTireWearEvent(relatedCar=c.relatedCar)
\end{lstlisting}
In einem C-SPARQL-Query führt der Abgleich der einzelnen Reifendruckwerte zu einem sehr langen Query:
\begin{lstlisting}[label={lst:scenario_tire_wear},caption={Erkennung von Verschleiß am Reifen}]
REGISTER STREAM getTireWear AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
CONSTRUCT {
[] rdf:type car:CarTireWearEvent
; car:relatedCar ?car .
}
FROM STREAM <http://example.org/carSim/stream/carStream> [RANGE 3s STEP 1s]
FROM <http://example.org/carSim/localDomainKnowledge>
WHERE {
?e rdf:type car:CarStatusEvent .
?e car:speed ?speed .
?e car:relatedCar ?car .
?car car:isCarModel ?carModel .
?carModel car:minimumTirePressure ?minTirePressure .
?e car:tirePressure1 ?tpress1 .
?e car:tirePressure2 ?tpress2 .
?e car:tirePressure3 ?tpress3 .
?e car:tirePressure4 ?tpress4 .
FILTER(?speed > 0)
FILTER(?tpress1 < ?minTirePressure || ?tpress2 < ?minTirePressure
|| ?tpress3 < ?minTirePressure || ?tpress4 < ?minTirePressure)
}
\end{lstlisting}
Auch dieser Query wird als Ereignisdatenstrom unter der URI \path{http://example.org/carSim/stream/getTireWear} zur weiteren Verarbeitung an der Engine registriert.
\end{itemize}
\paragraph{Tracking der Fahrer eines PKW}
Bevor nun die Ereignisdaten über Verschleiß an den PKW den Fahrern zugeordnet werden können, muss zunächst für die Pflege der Zuordnungsinformationen zwischen Fahrern und PKW gesorgt werden. Da ein Fahrzeug nur in einem bestimmten Zeitraum von einem bestimmten Fahrer geführt wird, müssen diese Informationen durchgehend aktualisiert werden, wenn sie im lokalen Domänenwissen vorgehalten werden sollen.
Um dies zu realisieren, werden zwei C-SPARQL-Queries geschrieben, die jeweils die einzelnen Ereignisse vom Typ \texttt{CarTakenEvent} und \texttt{CarReturnedEvent} aus dem Ereignisdatenstrom mit den Kundenereignissen extrahieren. Hierbei ist es zur Erhaltung der Datenkonsistenz wichtig, dass jedes dieser Ereignisse nur einmal zu einer Aktualisierung der lokalen Wissensbasis führt. Dafür wird in der entsprechenden Abfrage auf ein Tumbling Window gesetzt:
\begin{lstlisting}[label={lst:scenario_get_car_taken_events},caption={Selektion von Fahrerereignissen zur Aktualisierung der Wissensbasis}]
REGISTER QUERY getCarTakenEvents AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
SELECT ?driver ?car
FROM STREAM <http://example.org/carSim/stream/driverStream> [RANGE 2s TUMBLING]
WHERE {
?e rdf:type car:CarTakenEvent .
?e car:relatedCar ?car .
?e car:relatedUser ?driver .
}
\end{lstlisting}
Registriert man diesen Query an der Engine, so kann man mit Hilfe eines in Java geschriebenen Observers die lokale Wissensbasis um Einträge aktualisieren, in denen der Fahrer eines PKW entsprechend eingetragen oder --- wenn man dieses Verfahren für Ereignisse vom Typ \texttt{CarReturnedEvent} umsetzt --- ausgetragen werden. Die Details dazu finden sich in Kapitel~\ref{cpt:csparql_update_domain_knowledge}.
\paragraph{Zusammenfassung der Verschleißereignisse}
Um die einzelnen Ergebnisse aus den vorherigen Abfragen zusammenfassen zu können, ist zunächst erforderlich für die vier verschiedenen Ereignistypen eine gemeinsame Oberklasse \texttt{CarWearEvent} zu definieren. Hierzu werden der lokal vorliegenden TBox folgende RDFS-Daten hinzugefügt:
\begin{lstlisting}[label={lst:scenario_wear_event_tbox},caption={Klassenhierarchie für \texttt{CarWearEvent}s in RDFS}]
car:CarWearEvent rdf:type rdfs:Class .
car:CarWearEvent rdfs:subClassOf car:CarEvent .
car:CarEngineWearEvent rdf:type rdfs:Class .
car:CarEngineWearEvent rdfs:subClassOf car:CarWearEvent .
car:CarBrakeWearEvent rdf:type rdfs:Class .
car:CarBrakeWearEvent rdfs:subClassOf car:CarWearEvent .
car:CarHandbrakeWearEvent rdf:type rdfs:Class .
car:CarHandbrakeWearEvent rdfs:subClassOf car:CarWearEvent .
car:CarTireWearEvent rdf:type rdfs:Class .
car:CarTireWearEvent rdfs:subClassOf car:CarWearEvent .
\end{lstlisting}
Durch diese TBox ist es nun möglich, mit Hilfe von aktiviertem Reasoning alle Verschleißereignisse aus den vier erzeugten Ereignisdatenströmen zu extrahieren, ohne die einzelnen Verschleißereignistypen direkt nennen zu müssen. Um den jeweiligen Fahrer eines PKW den Verschleißereignissen zuordnen zu können, wird dieser über das lokale Domänenwissen unter Nutzung des Prädikats \texttt{car:isDrivenBy} ermittelt. Der folgende C-SPARQL-Query demonstriert dies:
\begin{lstlisting}
REGISTER STREAM getWearEvents AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
CONSTRUCT {
[] rdf:type car:CarWearEvent
; car:relatedCar ?car
; car:responsibleDriverName ?driverName
; car:responsibleDriverPhone ?driverPhone .
}
FROM STREAM <http://example.org/carSim/stream/getEngineWear> [RANGE 1s TUMBLING]
FROM STREAM <http://example.org/carSim/stream/getBrakeWear> [RANGE 1s TUMBLING]
FROM STREAM <http://example.org/carSim/stream/getHandbrakeWear> [RANGE 1s TUMBLING]
FROM STREAM <http://example.org/carSim/stream/getTireWear> [RANGE 1s TUMBLING]
FROM <http://example.org/carSim/localDomainKnowledge>
WHERE {
?e rdf:type car:CarWearEvent .
?e rdf:type ?type .
?e car:relatedCar ?car .
OPTIONAL {
?car car:isDrivenBy ?driver .
?driver car:hasName ?driverName .
?driver car:hasPhoneNumber ?driverPhone .
}
}
\end{lstlisting}
Da sämtliche \texttt{CarWearEvent}s für Erkennung von Wartungsbedarf eine Rolle spielen, wird auch diese Abfrage als Datenstrom unter der URI \path{http://example.org/carSim/stream/getWearEvents} an der Engine registriert.
Beobachtet man die Ergebnisse dieser Abfrage, so hat man die Möglichkeit, nutzungsbedingten Verschleiß dem Fahrer eines PKW zuzuordnen und umgekehrt natürlich auch, verschleißarmes Fahrverhalten durch günstigere Tarife zu belohnen.
\subsection{Erkennung von Wartungsbedarf am Fahrzeug}
Um Wartungsbedarf zu signalisieren soll eine Abfrage erzeugt werden, die Ereignisse vom Typ \texttt{CarMaintenanceNeededEvent} für jeden PKW liefert, für den ein Wartungsbedarf existiert. Ein Wartungsbedarf ist dann gegeben, wenn über einen größeren Zeitraum eine bestimmte Menge von \texttt{CarWearEvent}s ausgelöst wurde. In diesem Beispiel wurde eine Menge von 100 \texttt{CarWearEvent}s über einen Zeitraum von vier Stunden als Schwellwert festgelegt. Somit müssen über diesen Zeitraum alle Ereignisse vom Typ \texttt{CarWearEvent} nach PKW gruppiert und dann ausgezählt werden, um darauf aufbauend eine Kontextbedingung zu formulieren. In einer abstrakten CEP-Regel sieht dies wie folgt aus:
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION ($(CarWearEvent\ AS\ wearEvent)$)[WindowSize:4h,StepSize:5m]
$\wedge$ wearEvent.relatedCar AS car
$\wedge$ AGGREGATE(wearEvent, "relatedCar", COUNT) AS numberWearEvents
$\wedge$ numberWearEvents > 100
ACTION
new CarMaintenanceNeededEvent(relatedCar=car)
\end{lstlisting}
Übersetzt man dies nun in einen C-SPARQL-Query, so erhält man folgendes:
\begin{lstlisting}[label={lst:scenario_union_maintenance},caption={Signalisierung von Wartungsbedarf anhand von Ereignisaufkommen}]
REGISTER STREAM getMaintenanceEvents AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
CONSTRUCT {
[] rdf:type car:CarMaintenanceNeededEvent
; car:relatedCar ?car
; car:numberOfWearEvents ?numberWearEvents .
}
FROM STREAM <http://example.org/carSim/stream/getWearEvents> [RANGE 4h STEP 5m]
WHERE {
{
SELECT (?carWithWear AS ?car) (COUNT(?wearEvent) AS ?numberWearEvents)
WHERE {
?wearEvent rdf:type car:CarWearEvent .
?wearEvent car:relatedCar ?carWithWear .
}
GROUP BY ?carWithWear
HAVING (COUNT(?wearEvent) > 10)
}
}
\end{lstlisting}
\subsection{Unfallerkennung}
Die Erkennung von schweren Unfällen ist anhand von Ereignissen vom Typ \texttt{Car\allowbreak Airbag\allowbreak Triggered\allowbreak Event} relativ leicht, da mit sehr hoher Wahrscheinlichkeit davon ausgegangen werden kann, dass ein ausgelöster Airbag mit einem schweren Aufprall zusammenhängt. Die Erkennung kleinerer Kollisionen hingegen stellt selbst mit Daten von Schocksensoren eine Herausforderung dar und kann somit im Rahmen dieses Szenarios nicht durchgeführt werden. Es bleibt die direkte Auswertung der \texttt{Car\allowbreak Airbag\allowbreak Triggered\allowbreak Event}s, um Unfälle zu erkennen. Dabei sind nur Fahrzeuge interessant, die zur Zeit auch gefahren werden; für die also ein Fahrer eingetragen ist. Die abstrakte CEP-Regel dafür sieht wie folgt aus:
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION ($(CarAirbagTriggeredEvent\ AS\ airbagEvent)$)[WindowSize:5s,StepSize:5s]
$\wedge$ lookup("driver", airbagEvent.relatedCar) AS driver
ACTION
callEmergencyResponseService(driver=driver, relatedCar=airbagEvent.relatedCar)
\end{lstlisting}
Ein entsprechender C-SPARQl-Query sieht wie folgt aus:
\begin{lstlisting}[label={lst:scenario_react_to_airbag},caption={Selektion von Fahrern deren Airbag ausgelöst wurde}]
REGISTER QUERY getDriversWithAirbagTriggered AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
SELECT ?driver ?car
FROM STREAM <http://example.org/carSim/stream/carStream> [RANGE 5s TUMBLING]
FROM <http://example.org/carSim/localDomainKnowledge>
WHERE {
?airbagEvent rdf:type car:CarLockEvent .
?airbagEvent car:relatedCar ?car .
?car car:isDrivenBy ?driver .
}
\end{lstlisting}
Für diesen Query wird nun noch ein eigener Observer benötigt, der aus den Ergebnissen die betroffenen Fahrer und PKW extrahiert und damit einen Service anstößt, der solche Vorfälle behandeln soll.
\subsection{Prävention unbeabsichtigt wegrollender PKW}
Ein immer wieder auftauchendes Phänomen, vor dem frühzeitig gewarnt werden soll, betrifft PKW, welche ohne angezogene Handbremse abgestellt wurden und dann weggerollt sind. Eine abstrakte CEP-Regel hierfür sieht wie folgt aus:
\begin{lstlisting}[mathescape=true,label={},caption={}]
CONDITION ($(CarLockEvent\ AS\ lockEvent)\ \rightarrow\ (CarStatusEvent\ AS\ rollEvent$)
[WindowSize:15s,StepSize:1s]
$\wedge$ lockEvent.relatedCar = rollEvent.relatedCar
$\wedge$ rollEvent.locked = true
$\wedge$ rollEvent.handbrakeEngaged = false
$\wedge$ rollEvent.speed > 0
ACTION
...
\end{lstlisting}
Setzt man dies mit C-SPARQL um, so entsteht folgender Query:
\begin{lstlisting}[label={lst:scenario_detect_locked_rolling_cars},caption={Erkennung von unbeabsichtigt losgerollten PKW}]
REGISTER QUERY getLockedMovingCars AS
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX f: <http://larkc.eu/csparql/sparql/jena/ext#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX car: <http://example.org/carSim/carSimulationOntology#>
SELECT ?car
FROM STREAM <http://example.org/carSim/stream/carStream> [RANGE 15s STEP 1s]
WHERE {
?lockEvent rdf:type car:CarLockEvent .
?lockEvent car:relatedCar ?car .
?rollingEvent rdf:type car:CarStatusEvent .
?rollingEvent car:relatedCar ?car .
?rollingEvent car:speed ?speed .
?rollingEvent car:handbrakeEngaged ?handbrake .
?rollingEvent car:locked ?locked .
FILTER(?speed>0)
FILTER(?locked)
FILTER(!?handbrake)
FILTER(f:timestamp(?lockEvent,rdf:type,car:CarLockEvent) < f:timestamp(?rollingEvent,rdf:type,car:CarStatusEvent))
}
\end{lstlisting}
Beobachtet man die Ergebnisse dieses Queries, so erfasst man PKW, die aufgrund einer vergessenen Handbremse angefangen haben sich zu bewegen. Um jedoch schon \emph{präventiv} den Fahrer eines PKW vor einer vergessenen Handbremse zu warnen, muss lediglich das Kriterium der Geschwindigkeit aus der CEP-Regel entfernt werden. Somit würden dann alle PKW, die ohne angezogene Handbremse verschlossen wurden im Ergebnis auftauchen und es wäre leicht, über das Domänenwissen den Fahrer des PKW auszumachen, und ihn automatisch über die Situation zu benachrichtigen.
\section{Nutzung der C-SPARQL Engine in Java}
Nachdem die C-SPARQL-Queries zur Umsetzung der Anforderungen des Beispielszenarios aus Kapitel~\ref{cpt:scenario} feststehen, muss nun das Szenario implementiert werden. Das CSPARQL-ReadyToGoPack\footnote{Zu beziehen unter \url{http://streamreasoning.org/resources/c-sparql}} diente hierbei als Referenz. Die für die C-SPARQL-Engine benötigten Abhängigkeiten werden dabei durch das Werkzeug Maven bezogen und bereitgestellt.
\paragraph{Initialisierung der Engine}\label{cpt:csparql_domain_knowledge}
Zunächst wird eine Instanz der Engine benötigt. Diese muss dann initialisiert werden, wobei ein Parameter festlegt, ob die Funktion
\begin{lstlisting}
f:timestamp(?s,?p?o)
\end{lstlisting}
zum Ermitteln der Zeitstempel von Tripeln aus Ereignisdatenströmen zur Verfügung stehen soll.
\begin{lstlisting}
CsparqlEngine engine = new CsparqlEngineImpl();
engine.initialize(true);
\end{lstlisting}
Als nächstes wird das lokale Domänenwissen aus einer RDF-Datei im Pfad \texttt{data/\allowbreak car\allowbreak Simulation\allowbreak Abox.rdf} geladen und in einem lokalen, benannten Graphen mit der URI \path{http://example.org/carSim/localDomainKnowledge} abgelegt:
\begin{lstlisting}
engine.putStaticNamedModel(
"http://example.org/carSim/localDomainKnowledge",
CsparqlUtils.serializeRDFFile("data/carSimulationABox.rdf"));
\end{lstlisting}
\paragraph{Registrierung der RDF-Ereignisdatenströme}
Nun werden die zu verarbeitenden Ereignisdatenströme benötigt; sie müssen an der Engine registriert werden. Im Kontext der C-SPARQL-Engine wird ein Ereignisdatenstrom durch die Klasse \texttt{RdfStream} repräsentiert. Dieser bekommt bei seiner Konstruktion die URI übergeben, unter der der Datenstrom innerhalb von C-SPARQL-Queries abgefragt werden kann. Über die öffentliche Methode \texttt{put(RdfQuadruple quad)} können Quadrupel an den Datenstrom geschickt werden, der diese wiederrum an die Engine weitergibt. Mit Hilfe der Methode \texttt{registerStream(RdfStream stream)} wird der Datenstrom an der Engine registriert. Es folgt ein Beispiel für die grundlegende Verwendung von der Klasse \texttt{RdfStream}:
\begin{lstlisting}
RdfStream myRdfStream = new RdfStream("http://example.org/stream/myStream");
engine.registerStream(stream);
myRdfStream.put(new RdfQuadruple(
":subject",
":predicate",
":object",
System.currentTimeMillis()
));
\end{lstlisting}
\paragraph{Registrieren von Queries an der Engine}
Nachdem die Ereignisdatenströme an der Engine registriert sind und das lokale Domänenwissen erfolgreich hinterlegt wurde, können die C-SPARQL-Queries an der Engine registriert werden. Hierzu wird der Query als String an die Methode \texttt{registerQuery(String query, boolean useInference)} übergeben, um einen \texttt{CsparqlQueryResultProxy} zu erhalten:
\begin{lstlisting}
String query = "...";
CsparqlQueryResultProxy resultProxy = engine.registerQuery(query, false);
\end{lstlisting}
\paragraph{C-SPARQL Query als Ereignisdatenstrom registrieren}
Handelt es sich bei dem zu registrierenden Query um einen Ereignisdatenstrom (zu erkennen an der Präambel \texttt{REGISTER STREAM}, sowie der Verwendung des Schlüsselwortes \texttt{CONSTRUCT}), so ist auf Seite des Java-Codes ein kleiner Umweg notwendig, um die Ergebnisse des Queries wieder in die Engine einzuspeisen. Hierfür wird ein \texttt{RDFStreamFormatter} benötigt, der unter Angabe der URI des Datenstroms konstruiert wird. Dieser wird an der Engine als weiterer Ereignisdatenstrom registriert und schließlich als Observer an den ResultProxy übergeben:
\begin{lstlisting}
RDFStreamFormatter rdfStreamFormatter = new RDFStreamFormatter(streamUri);
engine.registerStream(rdfStreamFormatter);
resultProxy.addObserver(rdfStreamFormatter);
\end{lstlisting}
Nachdem dies vollbracht ist, können neue C-SPARQL-Queries nun unter Angabe der URI des neu registrierten Datenstroms auf diesen zugreifen. Einer mehrstufigen Auswertungskette steht so nichts mehr im Wege.
\paragraph{Beobachten der Ergebnisse von Queries}
Um nun die Ergebnisse der registrierten Abfragen zu erhalten, erzeugt man einen \texttt{Observer}, der den durch die Registrierung des Queries erhaltenen ResultProxy beobachtet:
\begin{lstlisting}
Observer o = new Observer() {
@Override
public void update(Observable o, Object arg) {
CsparqlQueryResultProxy resultProxy = (CsparqlQueryResultProxy) o;
RDFTable results = (RDFTable) arg;
Collection<RDFTuple> tuples = results.getTuples();
}
};
resultProxy.addObserver(o);
\end{lstlisting}
Wie aus dem Code ersichtlich ist, erhält man für jedes Abfrageergebnis die dazu gehörige Instanz des \texttt{CsparqlQueryResultProxy}, sowie eine \texttt{RDFTable}, die die dazugehörigen Ergebnisse enthält. Somit ist das Einsammeln der Abfrageergebnisse für eine manuelle Verarbeitung kein großes Problem.
\paragraph{Aktualisierung von Domänenwissen}\label{cpt:csparql_update_domain_knowledge}
In einigen Situationen ist es notwendig, Informationen aus dem lokalen Domänenwissen anzupassen. Auch hierfür bietet die C-SPARQL-Engine Möglichkeiten durch die Nutzung von SPARQL Queries. Der folgende Abschnitt zeigt die Zusammenstellung und Ausführung eines Queries, der Daten in die lokale Wissensbasis einfügt:
\begin{lstlisting}
String updateQuery = "PREFIX car: <http://example.org/carSim/carSimulationOntology#> "
+ "INSERT DATA { "
+ " GRAPH <http://example.org/carSim/localDomainKnowledge> { "
+ " :Driver#1 car:drives :Car#2 . "
+ " :Car#2 car:isDrivenBy :Driver#1 . "
+ " } "
+ "}";
engine.execUpdateQueryOverDatasource(updateQuery);
\end{lstlisting}
Auch das Löschen von Daten aus der lokalen Wissensbasis stellt kein Problem dar; ersetzt man aus dem gezeigten Query das \texttt{INSERT DATA} mit einem \texttt{DELETE DATA}, so werden die Daten wieder aus dem lokalen Graphen entfernt. Auf diese Weise wird für das Beispielszenario aus Kapitel~\ref{cpt:scenario} das Tracking der Fahrer realisiert.
\paragraph{Aktivierung und Konfiguration von Reasoning}\label{cpt:csparql_reasoning}
Für die Umsetzung von Reasoning wird in der C-SPARQL-Engine die Implementierung des \texttt{GenericRuleReasoner} aus Apache Jena verwendet. Im Rahmen dieses Szenarios wird diesem Reasoner ein Regelwerk zugeführt, welches die Axiome und Folgerungsregeln von RDFS implementiert.
Um Reasoning mit der C-SPARQL-Engine zu nutzen, müssen die folgenden zwei Schritte durchgeführt werden:
\begin{itemize}
\item \textbf{Aktivierung von Inferenz:}
Bei der Registrierung einer C-SPARQL-Abfrage an der Engine muss bei dem Aufruf der Methode \texttt{registerQuery(String query, boolean useInference)} angegeben werden, ob Inferenz mit dem Reasoner für diesen Query verwendet werden soll. Die folgende Zeile zeigt diesen Schritt, wobei der zweite Parameter ausschlaggebend für die Aktivierung ist:
\begin{lstlisting}
String query = "...";
CsparqlQueryResultProxy resultProxy = engine.registerQuery(query, true);
\end{lstlisting}
\item \textbf{Konfiguration des Reasoners:}
Hat man auf diese Weise einen Query an der Engine registriert, so muss als nächstes für den \texttt{CsparqlQueryResultProxy} Parameter angegeben werden, unter denen das Reasoning durchgeführt werden soll. Neben der Angabe des zu verwendenden Regelwerks, welches in diesem Fall RDFS implementiert, muss eine TBox mit Basisvokabular angegeben werden, die als Basis für die Schlussfolgerungen dient. Weiterhin ist es möglich zu konfigurieren, welche Logik zum Anstellen der Schlussfolgerungen verwendet werden soll. Hierbei gibt es drei Auswahlmöglichkeiten, die im Folgenden grob erklärt werden:
\begin{itemize}
\item \textbf{Forward Chaining}: Geht von den bereits existierenden Fakten aus und prüft anhand dieser Fakten, ob es Regeln gibt, die durch diese Fakten erfüllt sind. Ist dies der Fall, werden diese erfüllten Regeln verwendet, um daraus Wissen abzuleiten. Stellt das standardmäßige Vorgehen dar, falls keine Logik explizit angegeben wurde.
\item \textbf{Backward Chaining}: Stellt quasi das gegenteilige Vorgehen zu Forward Chaining dar. Begonnen wird bei einer Aussage, die darauf überprüft werden soll, ob sie zutrifft. Nun werden alle Regeln gesucht, die zu dieser Aussage führen und geprüft, ob diese erfüllt sind. Somit sind alle Bedingungen dieser Regeln wiederum Aussagen, die geprüft werden müssen. Dieser Ansatz setzt sich über weitere Regeln so weit fort, bis entweder ein erfüllender Fakt gefunden wurde, oder alle Regeln negativ getestet worden sind.
\item \textbf{Hybrid}: Ein Ansatz, welcher Forward Chaining und Backward Chaining kombiniert.
\end{itemize}
Für die Implementierung des Beispielszenarios dieser Arbeit fiel die Entscheidung auf den standardmäßig von der Engine verwendeten Ansatz des Forward Chaining.
\end{itemize}
Über die folgende Methode kann Reasoning für einen an der Engine registrierten Query aktiviert werden:
\begin{lstlisting}
updateReasoner(String queryId, String rulesFile,
ReasonerChainingType chainingType, String tBoxFile)
\end{lstlisting}
Die Einrichtung von Reasoning wurde mit folgendem Aufruf durchgeführt:
\begin{lstlisting}
engine.updateReasoner(
resultProxy.getSparqlQueryId()
CsparqlUtils.fileToString("data/rdfs.rules"),
CsparqlUtils.serializeRDFFile("data/carSimulationTBox.rdf"),
ReasonerChainingType.FORWARD
);
\end{lstlisting}
Nach dem Aufruf von \texttt{updateReasoner()} reichert die Engine nun die Ergebnisse des betroffenen Queries durch die im Reasoning ermittelten, impliziten Fakten an.
Tiefgehende Details über die Implementierung von Reasoning in der C-SPARQL-Engine können in \cite{barbieri:reasoning} nachgelesen werden. Weiterführend kann über die Umsetzung von Reasoning mit Hilfe von Complex Event Processing in der Masterarbeit von Stefan Lier\cite{hsh:reasoning} gelesen werden.
\section{Ergebnis}
Baut man all die C-SPARQL-Abfragen mit dem nötigen Java-Unterbau zusammen, so erhält man eine flexible Lösung zur Auswertung von RDF-Ereignisdatenströmen. Die Umsetzung der Anforderungen des gegebenen Beispielszenarios bereitete zwar einige Schwierigkeiten, dennoch konnten die meisten Anforderungen des Szenarios mit der C-SPARQL-Engine erfolgreich umgesetzt werden. Somit ist es möglich, eintretenden Verschleiß an geliehenen Fahrzeugen direkt dem aktuellen Fahrer des Fahrzeugs zuzuordnen und auf auftretendem Wartungsbedarf zeitnah zu reagieren. Auch die Erkennung von PKW, die ohne angezogene Handbremse abgestellt wurden ist machbar.
Lediglich mit der Abfrage aus Listing~\ref{lst:scenario_tire_wear} zur Erkennung von niedrigem Reifendruck gab es Probleme. Trotz mehrfacher Anpassungsversuche und dem Umstieg vom Datentyp \texttt{xsd:double} auf den Datentypen \texttt{xsd:integer} zur Repräsentation von Reifendruckangaben führte diese Abfrage leider nicht zu Ergebnissen. Selbst unter künstlichen Bedingungen, in denen die gemeldeten Reifendruckdaten immer unter den im Domänenwissen vorgehaltenen Grenzwerten lagen, führten nicht zum Erfolg. Es wird vermutet, dass es mit den logischen Operatoren \texttt{\&\&} und \texttt{||} innerhalb von \texttt{FILTER()}-Anweisungen noch unbekannte Probleme geben könnte.
\paragraph{Technische Schwierigkeiten}
Da die C-SPARQL-Engine im Rahmen von zur Zeit Forschungsprojekten entwickelt wurde, die zur Zeit scheinbar abgeschlossen sind, ist bisher noch keine aktive Weiterentwicklung der Software in Sicht\footnote{Unter \url{https://github.com/streamreasoning/CSPARQL-engine/issues} sind die aktuell bekannten Probleme der Software einsehbar.}. Für die Umsetzung des Beispielszenarios im Rahmen dieser Arbeit wurde die C-SPARQL-Engine in der Version 0.9.6 verwendet, da die aktuellste Version (0.9.7) für die Nutzung von Reasoning aufgrund von Ausnahmefehlern innerhalb der Engine nicht geeignet ist.
Die Implementation des Sprachparsers für C-SPARQL-Queries enthält weiterhin einen Fehler, durch den an einigen Stellen in der Abfrage Bezeichner, die das Wort \enquote{Stream} beinhalten, fälschlicherweise als das Token \texttt{STREAM} erkannt werden, worauf die Abfrage mit einer Exception zurückgewiesen wird. Ob es womöglich weitere dieser False Positives gibt, ist bisher unklar; sollte man jedoch auf eine scheinbar ungerechtfertigte Fehlermeldung des Parsers stoßen, so kann es hilfreich sein, diesen Fehler im Hinterkopf zu behalten.
Ein weiteres Problem besteht darin, dass die Funktion \texttt{f:timestamp()} Anstelle des erwarteten Zeitstempels eines gegebenen Tripels dann ein \texttt{false} zurückgibt, wenn das als Parameter übergebene Tripel einen Literalwert (also eine Zeichenkette, Ganzzahl oder ähnliches) enthält. Dieses Problem konnte jedoch umschifft werden, da die Zeitstempel für Ereignisdaten grundsätzlich über Tripel mit ihrer Typangabe mit \texttt{rdf:type} abgefragt wurden.
Weiterhin erwähnenswert ist die Besonderheit, dass die Engine keine Abfragen mit dem Schlüsselwort \texttt{CONSTRUCT} in Kombination mit der Verwendung von Aggregationen und \texttt{GROUP BY} erlaubt; dies wird mit einer Exception aus Apache Jena mit der Nachricht \enquote{SELECT * not legal with GROUP BY} quittiert. Abhilfe kann hier die Verwendung von Subqueries innerhalb der \texttt{WHERE}-Klausel schaffen, von dem das Ergebnis dann ohne Probleme innerhalb der \texttt{CONSTRUCT}-Klausel verwendet werden kann. Listing~\ref{lst:scenario_union_maintenance} zeigt diesen Ansatz in der Praxis.
Zuletzt ist die Registrierung von Abfragen als Ereignisdatenstrom durch den Umweg über einen \texttt{RdfStreamFormatter} ein Punkt, der in Zukunft durch eine Verbesserung der Implementierung der Engine abgeschafft werden könnte.
\paragraph{Korrektheit der Ergebnisse}
Sofern ein Query selbst syntaktisch korrekt ist, kann er dennoch logische Fehler enthalten, die zu fehlerhaften (oder gar keinen!) Ergebnissen führen können. Solche Fehler können nur dann erkannt werden, wenn die Korrektheit der Ergebnisse einer Abfrage zuverlässig geprüft werden kann. Eine Abhilfe kann hier die Nutzung von zuvor definierten Testdatenströmen bieten, die speziell für eine Abfrage zugeschnittene Ereignisdaten schicken, die in dieser Abfrage ein fest definiertes Ergebnis auslösen. Auf diese Weise ist es möglich, die Korrektheit einer Abfrage zu garantieren; allerdings bringt diese Methode einen stark erhöhten Aufwand mit sich. Für die Verarbeitung von Ereignisdatenströmen in großem Stil empfiehlt sich der erhöhte Aufwand für Tests jedoch sehr, da gerade bei mehrstufigen Verarbeitungsprozessen fehlerhafte Zwischenergebnisse nur schwer entdeckt werden können.
\chapter{Fazit}\label{cpt:conclusion}
In Betrachtung der Ergebnisse, die durch die Implementierung des Szenarios mit der C-SPARQL-Engine erzielt wurden, bietet die C-SPARQL-Engine viel Potential, um die Verarbeitung von RDF-Ereignisdatenströmen praktisch umzusetzen. Gerade der stark erleichterte Zugriff auf lokal hinterlegtes Domänenwissen ist im Vergleich zu der traditionellen CEP-Engine Esper ein großer Pluspunkt. Dem ist natürlich der vergleichsweise stark erhöhte Umfang der C-SPARQL-Queries, der durch die strukturelle Natur der RDF-Daten bedingt ist, entgegen zu setzen.
Aufgrund der Erfahrungen, die bei der Erstellung dieser Arbeit mit der C-SPARQL-Engine gemacht wurden, wird die Engine mit einigen Einschränkungen als tauglich für den Einsatz in der Praxis für kleine bis mittelgroße Projekte angesehen. Der Einstieg in die Verwendung der Engine und die C-SPARQL Sprache ist zu Beginn leicht. Jedoch stößt man bei der Formulierung von C-SPARQL-Queries häufig auf Situationen, in denen die einem bereits bekannten Sprachkonstrukte auf den aktuellen Kontext nicht passen. Häufig muss dann für die Formulierung von Queries auf Dokumentationen von C-SPARQL und SPARQL zurückgegriffen werden.
Für rein didaktische Zwecke kann die C-SPARQL-Engine auf jeden Fall verwendet werden, um in kleinem Rahmen die Verarbeitung von RDF-Ereignisdatenströmen mit Complex Event Processing zu erproben.
\section{Ausblick}
Sollte die Engine in Zukunft noch weiterentwickelt werden, bietet sie durchaus viel Potential: Behebt man die bisher bekannten Fehler und fügt eine detaillierte Dokumentation der konkret implementierten C-SPARQL-Features bei, so ergäbe sich ein gutes Paket für Einsteiger in die Thematik, die ihre Zeit nicht mit Debugging der Engine oder weiterführenden Recherchen verbringen wollen.
Zuletzt wäre auch denkbar, dass die Engine Reasoning auf Basis einer TBox mit OWL-Ontologien anbieten könnte. Die höhere Mächtigkeit von OWL-Ontologien im Vergleich zu Vokabular auf RDFS-Basis würde für deutlich höheren Komfort bei der Formulierung von C-SPARQL-Queries sorgen, da unter Umständen ein Teil der Logik einer Abfrage durch die mit OWL-Reasoning erhaltenen Daten entfallen würde.
\begin{comment}
\chapter*{Dummy-Kapitel für Tests und Notizen}
\textcolor{red}{Dieses Kapitel fliegt am Ende natürlich raus.}
Sil-ben ge-trenn-t mit ei-nem Strich
C--SPARQL (Zwei Striche ergeben einen Bindestrich)
Und dann --- neben vielen anderen Zeichen --- gibt es mit drei Strichen den Gedankenstrich.
Mit \enquote{enquote} wird Text in Anführungszeichen gesetzt, aber manchmal ist vielleicht der Einsatz von \texttt{texttt} sinnvoll. Im \textbf{Notfall} kann auch \textbf{textbf} genutzt werden. Dann gibt es noch \textit{textit}, \textsc{textsc}, \textsf{textsf} und \textsl{textsl}.
\begin{figure}[htbp]
\centering
\includegraphics[width=\textwidth]{img/aggregation-patternmatching.pdf}
\caption{Aggregation und Mustererkennung auf einem Ereignisstrom}
\label{fig:aggregation_patternmatching}
\end{figure}
Quellenreferenzen
\begin{itemize}
\item \cite{hsh:cep}[Einstieg in CEP mit Beispielen aus Esper-Welt]
\item \cite{hsh:integrating}[Esper vs C-SPARQL CEP ohne Reasoning]
\item \cite{barbieri:reasoning}[Ansatz für Reasoning auf RDF-Strömen mit C-SPARQL]
\item \cite{barbieri:querying}[Grundlagen C-SPARQL für CEP]
\item \cite{cqels:stream}[CQELS-Paper]
\item \cite{ep:etalis}[ETALIS-Paper]
\item \cite{ep:unified}[EP-SPARQL-Paper]
\item \cite{man:owl}[Owl Reasoning Examples]
\item \cite{w3c:sparql}[W3C zu SPARQL]
\item \cite{iao:esper}[Marktübersicht Real-Time Monitoring Software] <--- NOT CITED YET, nur für Esper relevant gewesen
\item \cite{w3c:turtle}[Die Turtle-Notation]
\item \cite{hitzler:semanticweb}[Grundlagen Semantic Web]
\item \cite{cqels:native}[Mehr CQELS]
\item \cite{barbieri:csparql}[Erstes C-SPARQL Proposal]
\end{itemize}
\end{comment}
%%% Ende inhaltlicher Inhalt! %%%
% Literaturverzeichnis
\clearpage
% Schlüssel als Buchstaben
\bibliographystyle{alpha}
\bibliography{Literaturverweise}
% Und JETZT zum Inhaltsverzeichnis hinzufügen. Geil!
\addcontentsline{toc}{chapter}{Literaturverweise}
\end{document}
% Nothing beyond this line!
% Referenz auf Bibtex mit Kommentar
% \cite{robbins:gawk}[Siehe ab S.95]
% Einbinden von Tex-Files
%\input{abkuerz.tex}
%\input{einfuehrung.tex}
% Einbinden von größeren Tex-Files, z.B. Kapiteln
%\include{normen}
%\include{aufbau}
%\include{zitieren}
%\include{form}
%\include{allgtips}