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:

  1. Öffnen Sie die Datei "log4j2.xml" im Portalverzeichnis internal/cfg mit einem Texteditor Ihrer Wahl.

  2. 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"/>
  3. Ä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"/>
  4. Führen Sie einen Neustart des Portal-Dienstes durch.

  5. 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:

  • Firefox RESTClient

    Mit diesem Firefox-Plugin, das Sie hier herunterladen können, lassen sich OData-Requests innerhalb des Browser ausführen und analysieren. Dies bietet sich vor allem für eine Fehleranalyse an. Dazu kann aus dem Intrexx-portal.log bei aktiviertem Request-Tracing der OData-HTTP-Request kopiert, im REST-Client ausgeführt und im Fehlerfall angepasst werden, um nicht unterstützte Funktionen zu vermeiden.

  • 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();

Weitere Informationen

Allgemeines

Systemvoraussetzungen

Daten konsumieren

Daten anbieten

Integration in Applikationen

Verwendung in Prozessen

Expert-Settings