Connector für OData - Appendix
Troubleshooting
Fehlermeldungen
OData-Anbieter
Sollten während einer OData-Request-Verarbeitung Fehler auftreten, werden aus Sicherheitsgründen keine Fehlerdetails an den Client zurückgegeben. Stattdessen wird je nach Art des Fehlers eine Antwort mit einem entsprechenden HTTP-Fehlercode generiert. Tritt der Fehler innerhalb der Businesslogik auf, so enthält das Portal-Server-Log unter Umständen detaillierte Fehlermeldungen. Sollte der Fehler aber schon zu Beginn der Request-Verarbeitung auftreten, gibt es zur Analyse nur die Möglichkeit, den detaillierten Fehlerbericht in die Antwort an den Client einzubetten. Dazu muss die Log4j-Konfiguration des Intrexx-Portal-Servers so angepasst werden, dass als Log-Level für das OData-Producer-Modul "DEBUG" anstatt "INFO" verwendet wird. Öffnen Sie die Datei log4j.properties im Portalverzeichnis internal/cfg in einem Texteditor. Suchen Sie in der Datei nach der Zeile "OData Producer" und ändern Sie in der nächsten Zeile den Wert INFO zu DEBUG. Nach dem Speichern der Datei muss der Portal-Server neu gestartet werden, um die detaillierten Fehlermeldungen zu aktivieren.
OData-Konsument
Sollten während eines OData-Requests Fehler auftreten, versucht Intrexx die Fehlermeldungen aus der Antwort des Services zu ermitteln und im Browser anzuzeigen. Dies ist nicht immer in allen Fällen möglich. Für eine detailliertere Fehleranalyse bietet es sich deshalb an, das OData-Request-Tracing im Intrexx Portal-Server zu aktivieren.
Request-Tracing und Fehlerprotokollierung
Bei aktiviertem Request-Tracing werden sowohl die OData-Requests als auch Responses im Detail in die Intrexx-Portal-Logdatei geschrieben. Für Requests besteht ein Eintrag aus der HTTP-Aktion, der URL, den Query-Options, den Request-Headern und dem XML-Body. Bei Antworten werden die HTTP-Header und der Response-XML-Body ausgegeben. Aktiviert wird das Tracing wie folgt:
-
Öffnen Sie die Datei "log4j2.xml" im Portalverzeichnis internal/cfg mit einem Texteditor Ihrer Wahl.
-
Navigieren Sie zum Abschnitt "logging for OData consumer":
<!-- logging for OData consumer --> <Logger name="de.uplanet.lucy.server.odata.consumer" level="info" additivity="false"> <AppenderRef ref="DailyFile"/>
-
Ändern Sie den Wert "info" auf "debug".
<!-- logging for OData consumer --> <Logger name="de.uplanet.lucy.server.odata.consumer" level="debug" additivity="false"> <AppenderRef ref="DailyFile"/>
-
Führen Sie einen Neustart des Portal-Dienstes durch.
-
Bei jeder OData-Aktion werden nun die Request-Details in der portal.log-Datei im Portalverzeichnis log protokolliert.
Beispiel für einen Request/Response-Tracing-Eintrag:
DEBUG 2014-05-23 09:47:57,384
OData response:
Status: 200
DataServiceVersion: 1.0;
Content-Length: 5074
Server: Microsoft-IIS/8.0
Date: Fri, 23 May 2014 07:47:57 GMT
Content-Type: application/atom+xml;charset=utf-8
<feed xml:base="https://sharepoint2013/myTest/_vti_bin/listdata.svc/" xmlns="https://www.w3.org/2005/Atom" xmlns:d="https://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="https://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<title type="text">MyTestTasks</title> <id>https://sharepoint2013/myTest/_vti_bin/listdata.svc/MyTestTasks</id>
<updated>2014-05-23T07:47:57Z</updated>
<link href="MyTestTasks" rel="self" title="MyTestTasks" />
<entry m:etag="W/"6"">
<id>https://sharepoint2013/myTest/_vti_bin/listdata.svc/MyTestTasks(2)</id>
<title type="text">MyTask</title>
<updated>2014-04-28T14:54:43+02:00</updated>
<author>
<name />
</author>
<link href="MyTestTasks(2)" rel="edit" title="MyTestTasksItem" />
<category scheme="https://schemas.microsoft.com/ado/2007/08/dataservices/scheme" term="Microsoft.SharePoint.DataService.MyTestTasksItem" />
<content type="application/xml">
<m:properties>
<d:ID m:type="Edm.Int32">2</d:ID>
<d:Operationname>MyTask</d:Operationname>
<d:Startdate m:type="Edm.DateTime">2014-04-11T00:00:00</d:Startdate>
<d:Duedate m:type="Edm.DateTime">2014-04-29T00:00:00</d:Duedate>
</m:properties>
</content>
</entry>
<entry m:etag="W/"5"">
<id>https://sharepoint2013/myTest/_vti_bin/listdata.svc/MyTestTasks(3)</id>
<title type="text">PortalVisions - Infrastructure</title>
<updated>2014-04-25T17:29:00+02:00</updated>
<author>
<name />
</author>
<link href="MyTestTasks(3)" rel="edit" title="MyTestTasksItem" />
<category scheme="https://schemas.microsoft.com/ado/2007/08/dataservices/scheme" term="Microsoft.SharePoint.DataService.MyTestTasksItem" />
<content type="application/xml">
<m:properties>
<d:ID m:type="Edm.Int32">3</d:ID>
<d:Operationname>PortalVisions 2014 - Infrastruktur</d:Operationname>
<d:Startdate m:type="Edm.DateTime">2014-04-23T00:00:00</d:Startdate>
<d:Duedate m:type="Edm.DateTime">2014-04-28T00:00:00</d:Duedate>
</m:properties>
</content>
</entry></feed>
Um für den OData-Provider eine detaillierte Protokollierung in eine Logdatei zu aktivieren, öffnen Sie die Datei "portal.wcf" im Portalverzeichnis internal/cfg und fügen dem Abschnitt "Java Additional Parameters" folgende Zeile hinzu:
wrapper.java.additional.12=-Djava.util.logging.config.file= <intrexx>\org\<portal>\internal\cfg\odata\producer\logging.properties
Ersetzen Sie dabei <intrexx> und <portal> mit den Werten Ihrer Portalumgebung und führen Sie nach dem Speichern der Datei einen Neustart des Portal-Servers durch. Anschließend werden die OData-Anfragen inklusive Inhalt protokolliert. Weitere Einstellungen können in der Datei "logging.properties" vorgenommen werden.
Nicht unterstützte OData-Funktionen
Während Intrexx-Unterstützung für alle wesentlichen Funktionen der OData-Spezifikation Version 2.0 anbietet, kann es unter Umständen vorkommen, dass ein Service bestimmte Funktionen nicht unterstützt. In diesem Fall kommt es entweder zu einem Fehler oder eine Abfrage liefert nicht das erwartete Ergebnis. Da die OData-Spezifikation den implementierenden Diensten einen relativ großen Spielraum bietet, was die Unterstützung von Features betrifft, kann in einem solchen Fall nur die Intrexx-Applikation angepasst werden, so dass nur vom Service unterstützte Funktionen verwendet werden. Beispiele für solche Fälle sind Filterdefinitionen, Seitennavigation (Pagination) oder Sortierung. Über die entsprechenden Expert-Settings lassen sich problematische OData-Features deaktivieren. Falls bestimmte Filterdefinitionen nicht unterstützt werden, muss der Filter in Intrexx entsprechend angepasst bzw. vereinfacht werden.
SSL-Verbindungen
Für SSL-Verbindungen zwischen dem Intrexx-Portal-Server und einem OData-Service muss das Zertifikat der Certificate Authority, die das Service-Zertifikat ausgestellt hat, dem Zertifikatsspeicher des Intrexx-Portal-Servers hinzugefügt worden sein. Eine Ausnahme bilden selbstsignierte Zertifikate, die nicht von einer bekannten Certificate Authority ausgestellt wurden. Um SSL-Verbindungen zu Diensten mit selbstsignierten Zertifikaten zu ermöglichen, muss in diesem Fall im Intrexx-Portal-Server die Prüfung der Certificate-Chain deaktiviert werden. Dies ist auf Service-Ebene über eine System-Property möglich. Öffnen Sie die Datei "portal.cfg" im Portalverzeichnis internal/cfg mit einem Texteditor und fügen Sie dem Abschnitt <environment> einen neuen <systemProperty>- Eintrag hinzu:
<systemProperty name = "de.uplanet.lucy.server.odata.consumer.ssl.allowSelfSignedCerts.<SERVICE_GUID>;" value="true"/>
Der Platzhalter <SERVICE_GUID> ist mit der GUID des OData-Services zu ersetzen. Die GUID können Sie der Service-Konfigurationsdatei im Portalverzeichnis internal/cfg/odata entnehmen. Nach dem Speichern der portal.cfg-Datei muss der Intrexx-Portal-Server-Dienst neu gestartet werden, damit die Änderungen wirksam werden.
Beenden von Intrexx-OData-Sessions
Intrexx-Sitzungen, die über den OData-Provider eröffnet wurden, werden entweder durch einen automatischen Timeout oder auf Anfrage des Clients beendet. Im Fall von Timeouts wird zwischen anonymen und authentifizierten Sitzungen unterschieden. Standardmäßig wird für anonyme Sitzungen ein Timeout von 60 Sekunden verwendet, bei Benutzersitzungen wird die Sitzung nach 10 Minuten ohne Aktivität automatisch beendet. Beide Timeout-Varianten können über eine System Property eingestellt werden. Um die Default-Werte zu ändern, können in der Datei "portal.cfg" im Portalverzeichnis internal/cfg dem Abschnitt "environment" zwei neue <systemProperty> Einträge hinzugefügt werden:
<systemProperty name="de.uplanet.lucy.server.odata.producer.anonymousSessionTimeoutMilliseconds" value="70000"/>
<systemProperty name="de.uplanet.lucy.server.odata.producer.authenticatedSessionTimeoutMilliseconds" value="700000"/>
Bitte beachten Sie, dass die Werte für den Session-Timeout in Millisekunden anzugeben sind. Die Änderungen werden nach einem Neustart des Portal-Dienstes übernommen.
Die zweite Möglichkeit zum Beenden einer Sitzung besteht in einem Logout-Request durch den OData-Client. Dazu sendet der Client einen Request an den Server, wobei der URL mit dem Pfad $logout enden muss. Mit der im HTTP-Header als Cookie angegebenen Session-ID wird die Intrexx-Session identifiziert und automatisch beendet.
Anhang
OData-Spezifikation
Eine Beschreibung des OData-Protokolls sowie die OData-Spezifikation erhalten Sie hier.
OData-Tools
Folgende Tools haben sich bei der Anwendungserstellung und Problemanalyse als hilfreich erwiesen:
-
Postman API Client
Alle Informationen dazu finden Sie hier.
-
LinqPad
Mit LinqPad, das Sie hier herunterladen können, lassen sich Abfragen auf einen OData-Service mit der Abfragesprache Linq ausführen und visualisieren.
-
Silverlight-OData-Explorer
Ein weiteres Tool zur Ausführung und Analyse von OData-Abfragen im Browser (benötigt Microsoft-Silverlight-Plugin), das Sie hier herunterladen können.
-
Microsoft Visual Studio 2010
Mit der Entwicklungsumgebung Visual Studio 2010 von Microsoft lassen sich mit geringem Aufwand eigene OData-Services auf Basis bestehender Datenbanken generieren und über den Microsoft Internet Information Server bereitstellen. Hier können Sie sie herunterladen.
Reverse-Proxy-Konfiguration
Aus technischen Gründen ist es derzeit nicht möglich, Portal-Server als auch OData-Services unter dem gleichen TCP-Port zu betreiben. Daher muss für Intrexx-OData-Services ein anderer Port als der Standard-HTTP-Port 80 (bzw. 443 für SSL-Verbindungen) gewählt werden, wenn das Portal unter diesem erreichbar sein soll. Um sowohl den Portal-Server als auch OData-Services unter einem einheitlichen Port zu betreiben, bietet sich der Einsatz eines Reverse-Proxys an. Dabei handelt es sich um einen vorgeschalteten Webserver, der Benutzeranfragen empfängt und auf Basis eines Regelwerks an das entsprechende Backendsystem weiterleitet. Es gibt verschiedene freie und kommerzielle Reverse-Proxy-Lösungen. Beispielhaft wird im Folgenden eine Implementierung mit dem freien und kostenlosen Webserver Nginx beschrieben. Es wird dabei davon ausgegangen, dass sowohl Nginx als auch der Intrexx-Portal-Server auf dem gleichen physischen Server installiert sind und das Portal unter dem Port 8080 und OData-Services unter Port 9090 erreichbar sind. Die aktuelle Nginx-Version können Sie unter https://nginx.org beziehen. Nach dem Entpacken der Downloaddatei befindet sich die Konfigurationsdatei nginx.conf im Unterordner /conf. Ersetzen Sie diese mit folgender Beispielkonfiguration (eine Kopie der Datei befindet sich im Installationsverzeichnis adapter/odata/nginx).
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events
{
worker_connections 1024;
}
http
{
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
#upstream odata
{
# server 127.0.0.1:9090; #jetty
#
}
#upstream intrexx
{
# server 127.0.0.1:8080; #iis/tomcat
#
}
## IX/OData reverse proxy##
server
{
listen *:80;
#server_name $hostname;
#access_log /var/log/nginx/log/www.example.access.log main;
#error_log /var/log/nginx/log/www.example.error.log;
root html;
index index.htm index.htm;
## send request back to intrexx ##location /ix/
{
#rewrite /ix/(.*) /$1 break;
proxy_pass https://127.0.0.1:8080;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Host $hostname;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Server $hostname;
}
## send request back to odata ##
location /odata/
{
#rewrite /odata/(.*) /$1 break;
proxy_pass https://127.0.0.1:9090;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;proxy_buffering off;
proxy_set_header Host $hostname;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Server $hostname;
}
}
}
Passen Sie die Konfigurationsdatei gegebenenfalls Ihrer System-/Netzwerkumgebung an und speichern Sie die Datei. Anschließend kann der Nginx-Server über die Datei nginx.exe im Hauptverzeichnis gestartet werden. Testen Sie die Erreichbarkeit des Intrexx-Portals sowie der OData-Services unter der einheitlichen Adresse mit Port 80. Nun sollte noch sichergestellt werden, dass eingebettete URLs in OData-Dokumenten (wie z.B. Links zu in Beziehung stehenden Datensätzen) den korrekten Endpoint-URL enthalten. Normalerweise wird für die URLs der Name und Port des OData-Servers verwendet. Dieser weicht beim Einsatz eines Reverse-Proxys aber vom Endpunkt-URL ab. Daher wird ein dynamischer Mechanismus benötigt, der zur Laufzeit die korrekte Endpunkt-URL für OData-Adressen verwendet. Um dies zu erreichen, sendet der Reverse-Proxy-Server seinen Hostname und Port über spezielle Request-Header (X-Forwarded-*) an das Backend-System. Dieses kann die Header auslesen und damit den für Clients relevanten URL bilden. Um den Mechanismus zu aktivieren, gibt es in der OData-Server-Konfiguration den Parameter "Host". Tragen Sie hier entweder [X-Forwarded] ein, um den Host aus den Request-Headern zu ermitteln, oder geben Sie direkt den Hostnamen und Port im Format hostname:port des Reverse-Proxys an (wodurch Header-Werte überschrieben werden).
OData-Abfragen in Groovy-Skripten
Derzeit gibt es noch keine öffentliche Intrexx-Groovy-API für den Zugriff auf OData in Groovy-Skripten. Es ist allerdings möglich, die interne OData-API für Groovy freizuschalten. Diese garantiert allerdings nicht Kompatibiltät zu zukünftigen Intrexx-Versionen und sollte daher nur nach Absprache mit Ihrem INTREXX Kundenberater verwendet werden. Um den Zugriff auf die internen Klassen zu aktivieren, editieren Sie die Datei scripting.cfg im Portalverzeichnis internal/cfg/scripting und tragen die untenstehende Zeilen ein:
<?xml version="1.0" encoding="UTF-8"?>
<scripting
xmlns="urn:schemas-unitedplanet-de:lucy:server:scripting"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<scriptable name="de.uplanet.lucy.server.odata.consumer" type="package" />
<scriptable name="de.uplanet.lucy.server.odata.consumer.cfg" type="package" />
<scriptable name="de.uplanet.lucy.server.odata.consumer.jersey" type="package" />
<scriptable name="de.uplanet.lucy.server.odata.consumer.sharepoint" type="package" />
</scripting>
Anschließend muss der Portal-Server-Dienst neu gestartet werden. Die Klassen aus den oben aufgeführten Packages können nun in Groovy importiert werden.
OData Query
import de.uplanet.lucy.server.odata.consumer.cfg.*
import de.uplanet.lucy.server.odata.consumer.jersey.*
import org.odata4j.core.*
def cfg = ODataConsumerRegistry.getInstance().getConsumerConfiguration('719E5ABE4C8BB29D1B7AFE010E03D46F6F417295'); //cfgGuid
def consumer = ODataConsumerFactory.INSTANCE.createConsumer(cfg,
'A427ABD974F8FC245B77B6CB027EE2486A16D338', //serviceGuid
'7312F993D0DA4CECCA9AE5A9D865BE142DE413EA') //userGuid
List<OEntity> l_entities = l_consumer.getEntities("Product")
.filter("UnitPrice gt 2000 and UnitPrice lt 5000")
.orderby("UnitPrice desc")
.top(0)
.limit(10)
.execute().toList();
l_entities.forEach { record ->
def l_val1 = record.getProperty("Company").getValue()
def l_val2 = record.getProperty("Website").getValue()
}
OData Update
import de.uplanet.lucy.server.odata.consumer.cfg.*
import de.uplanet.lucy.server.odata.consumer.jersey.*
import org.odata4j.core.*
try
{
def cfg = ODataConsumerRegistry.getInstance().getConsumerConfiguration('719E5ABE4C8BB29D1B7AFE010E03D46F6F417295'); //Service configuration GUID from service configuration XML (internal/cfg/odata)
def consumer = ODataConsumerFactory.INSTANCE.createConsumer(cfg,
'A427ABD974F8FC245B77B6CB027EE2486A16D338', //Service Guid from service configurations XML (internal/cfg/odata)
'7312F993D0DA4CECCA9AE5A9D865BE142DE413EA') //User Guid for static user otherwise null for currently logged in user
def keys = [:]
keys['KeyFeldName1'] = "Value1" // use the Java data type here for the corresponding OData data type
keys['KeyFeldName2'] = "Value2"
keys['KeyFeldName3'] = "Value3"
def entityKey = OEntityKey.create(keys)
def entitySet = "" // Name des OData Entity Sets
// Prepare fields for update
def prop1 = OProperties.string("PropertyName1", "Wert") // see http://odata4j.org/v/0.7/javadoc/org/odata4j/core/OProperties.html
def prop2 = OProperties.int32("PropertyName2", 2)
def prop3 = OProperties.double_("PropertyName3", 3.0d)
consumer.mergeEntity(entitySet, entityKey)
.properties(prop1, prop2, prop3)
.execute()
}
catch (e)
{
g_log.error("Cannot update", e)
}
OData delete
import de.uplanet.lucy.server.odata.consumer.cfg.*
import de.uplanet.lucy.server.odata.consumer.jersey.*
import org.odata4j.core.*
try
{
def cfg = ODataConsumerRegistry.getInstance().getConsumerConfiguration('719E5ABE4C8BB29D1B7AFE010E03D46F6F417295'); //Service configuration GUID from service configuration XML (internal/cfg/odata)
def consumer = ODataConsumerFactory.INSTANCE.createConsumer(cfg,
'A427ABD974F8FC245B77B6CB027EE2486A16D338', //Service GUID from service configuration XML (internal/cfg/odata)
'7312F993D0DA4CECCA9AE5A9D865BE142DE413EA') //User GUID, otherwise currently logged in portal user
def keys = [:]
keys['KeyFeldName1'] = "Value1" // use the Java data type here for the corresponding OData data type
keys['KeyFeldName2'] = "Value2"
keys['KeyFeldName3'] = "Value3"
def entityKey = OEntityKey.create(keys)
def entitySet = "" // Name of the OData entity set
consumer.deleteEntity(m_strEntitySet, entityKey).execute()
}
catch (e)
{
g_log.error("Cannot delete", e)
OData Media-Link
import de.uplanet.lucy.server.odata.consumer.method.*
import java.nio.file.Files
def l_files = g_request.getUploadFiles();
def l_uploadFiles = l_files.getFiles("odataMediaResource"); // File control name
def l_httpMethod = "POST" // or "PUT" for update or "DELETE"
def l_userGuid = null // Intrexx user GUID with connected SAP Gateway account (static user) or null for interactive login
if (l_uploadFiles.isEmpty())
throw new Exception("Upload file request variable is not available.");
def l_uploadFile = l_uploadFiles.get(0); // or run list for all files in control
if (!Files.exists(l_uploadFile.getUploadFile().toPath()))
throw new Exception("Upload file is not available.");
def l_mr =
new IODataMediaLinkEntryMethod.MediaResource(l_uploadFile.getUploadFile(),
l_uploadFile.getContentType(),
l_uploadFile.getOriginalFileName());
def l_method =
ODataMediaLinkEntryMethod.newBuilder(g_context,
g_rtCache.getDataGroups().get("DATAGROUP_GUID"), // DATAGROUP_GUID = GUID of the OData File DG
l_httpMethod,
g_record.getRecId(),
l_mr,
l_userGuid).build();
if ("PUT".equals(l_httpMethod))
{
l_method.updateMediaResource();
}
else if ("DELETE".equals(l_httpMethod))
{
l_method.deleteMediaResource();
}
else
{
def l_strLocation = l_method.createMediaResource();
if (l_strLocation != null)
return l_strLocation.substring(l_strLocation.indexOf('(') + 1, l_strLocation.indexOf(')')); // get ID of the new entity
else
return null;
}
def l_mediaResource = l_method.getMediaResource();
def l_bytes = l_mediaResource.getBytes(); // returns byte array
def l_contentType = l_mediaResource.getContentType();