Connector for OData

Troubleshooting

Error messages

OData provider

If errors occur during OData request processing, no details of the errors are returned to the client for security purposes. Instead, depending on the type of error, a reply is generated with a corresponding HTTP error code. If the error occurs within the Intrexx business logic, the Portal Server log will contain detailed error messages in some circumstances. However, if the error occurs right from the start of request processing, it is only possible to embed the detailed error report in the reply to the client, so that the error can be analyzed. To do this, the "log4j" configuration on the Intrexx portal server must be changed in such a way that it uses the "DEBUG" logging level instead of "INFO" for the OData Producer module. To do this, open the "log4j.properties" file in the portal directory \internal\cfg\ in a text editor. Look for the "OData producer" line in the file, and in the following line, change the value of "INFO" to "DEBUG" . After saving the file, restart the portal server to enable the higher level of detail in the error messages.

OData consumer

If errors occur during an OData request, Intrexx attempts to detect the error messages from within the reply from the service and to display them in the browser. This is not possible in every single case. For a more detailed error analysis it can therefore be advantageous to activate OData request tracing in the Intrexx portal server.

Request tracing and error logging

When request tracing is enabled, both the OData requests and responses are written in detail to the Intrexx portal log file. For requests, the entry consists of the HTTP action, the URL, the query options, the request headers and the XML body. For responses, the HTTP header and the response XML will be delivered. Tracing is activated as follows:

  1. Open the file "log4j2.xml" in the Portal Directory internal/cfg with a text editor of your choice.

  2. Navigate to the section "Logging for OData consumer":

    <!-- logging for OData consumer -->
    <Logger name="de.uplanet.lucy.server.odata.consumer" level="info" additivity="false">
    <AppenderRef ref="DailyFile"/>
  3. Change the value "info" to "debug".

    <!-- logging for OData consumer -->
    <Logger name="de.uplanet.lucy.server.odata.consumer" level="debug" additivity="false">
    <AppenderRef ref="DailyFile"/>
  4. Restart the portal service.

  5. For each OData action, the request details will now be logged in the "portal.log" file in the portal directory /log.

Example of a request/response tracing entry:

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>

You can also activate detailed logging for the OData Provider, which writes all OData replies to a log file. To do so, open portal directory \internal\cfg\portal.wcf and add the following line to the "Java Additional Parameters" section:

wrapper.java.additional.12=-Djava.util.logging.config.file= <intrexx>\org\<portal>\internal\cfg\odata\producer\logging.properties

Replace <INTREXX_HOME> and <PORTAL> with the specific values for your portal environment, then save the file and restart the portal server. Afterwards, the OData requests will be logged along with their contents. You can apply other settings in regard to logging in the "logging.properties" file.

Unsupported OData functions

Although Intrexx provides support for all significant functions in version 2.0 of the OData specification, it can occur that a service does not support certain functions. In this case, there will either be a failure, or a query will not deliver the expected result. Since the OData specification provides a relatively large scope for the services to be implemented in regard to feature support, if a feature proves to be unsupported, the only way to get around it is to adjust the Intrexx application to only use functions supported by the service. Examples of such cases are filter definitions, site navigation (pagination) or sorting. Using the corresponding expert settings, problematic OData features can be deactivated. If certain filter definitions are not supported, the filter must be adjusted accordingly in Intrexx, or simplified.

SSL connections

For SSL connections between the Intrexx portal server and an OData service, the Certificate Authority that provided the service certificate must be added to the certificate store of the Intrexx portal server. Self-signed certificates are an exception to this, which are not provided by a recognized Certificate Authority. In order to enable SSL connections to services with self-signed certificates, the check on the certificate chain must be deactivated. This is possible on the service level by using a system property. To do so, open the "portal.cfg" file in the portal directory /internal/cfg/ with a text editor and add a new <systemProperty> entry to the <environment> section:

<systemProperty name = "de.uplanet.lucy.server.odata.consumer.ssl.allowSelfSignedCerts.<SERVICE_GUID>;" value="true"/>

The "<SERVICE_GUID>" placeholder must be replaced with the OData service GUID. The GUID can be retrieved from the service configuration file in the portal directory internal/cfg/odata. After saving the "portal.cfg" file, the Intrexx portal service must be restarted for the changes to take effect.

Closing Intrexx OData sessions

Intrexx sessions that were opened via the OData Provider will be closed either by an automatic timeout or upon request by the client. In the case of timeouts, a distinction is made between anonymous and authenticated sessions. By default, a timeout of 60 seconds is used for anonymous sessions. For authenticated sessions, the session is automatically closed after 10 minutes without activity. Both timeout values can be changed via a system property. To change the default values, open the file "portal.cfg" in the portal directory internal/cfg in a text editor, navigate to the <environment> section, and add two new <systemProperty> entries:

<systemProperty name="de.uplanet.lucy.server.odata.producer.anonymousSessionTimeoutMilliseconds" value="70000"/>
<systemProperty name="de.uplanet.lucy.server.odata.producer.authenticatedSessionTimeoutMilliseconds" value="700000"/>

Please note that the values for the session timeout must be specified in milliseconds. The changes will be applied after the portal service is restarted.

The second way to close a session is to use a logout request from the OData client. This is done by having the client send a request to the server where the URL ends with the "$logout" path. The session ID, specified in the HTTP header as a cookie, is used to identify and automatically close the Intrexx session.

Attachment

OData specification

A description of the OData protocol as well as the OData specification can be found here.

OData tools

The following tools have been found to be helpful for the creation of applications, and problem analysis:

  • Firefox RESTClient

    This Firefox plugin, which you can download here, allows OData requests to be run and analyzed within the browser. This is useful, especially for error analysis. Thus, with Request Tracing enabled, the OData HTTP request may be copied from the Intrexx portal.log and executed in the REST client, and in cases of failure, can be adjusted to avoid unsupported functions. https://addons.mozilla.org/de/firefox/addon/restclient/

  • LinqPad

    "LinqPad", which you can download here, is used to run and visualize requests to an OData service using the "Linq" request language.

  • Silverlight OData Explorer

    A further tool for running and analyzing OData requests in the browser (requires Microsoft Silverlight Plugin), which you can download here.

  • Microsoft Visual Studio 2010

    The Visual Studio 2010 development environment from Microsoft lets custom OData services based on existing databases to be generated and provided over the Microsoft Internet Information Server with little effort. You can download this here.

Reverse proxy configuration

For technical reasons, it is not currently possible to run the portal server and OData services on the same TCP port. For this reason, another port must be selected for Intrexx OData services than the default HTTP port 80 (or 443 for SSL connections), if the portal needs to be available on that port. However, a reverse proxy can be used to run the portal server as well as OData services on the same port. This method uses a forward-facing web server that receives user requests and uses a set of rules to forward the requests to the corresponding back-end system. Various free and commercial reverse proxy solutions are available. The following section describes a sample implementation using the free Nginx web server. The example assumes that both Nginx and the Intrexx portal server are running on the same physical server, and that the portal is available on port 8080 and the OData services are available on port 9090. You can download the current version of Nginx from https://nginx.org. After extracting the downloaded file, the configuration file "nginx.conf" is located in the child folder "/conf". Replace the file with the following sample configuration (you can find a copy of the file in the Intrexx installation directory \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;
		}
	}
}

Adjust the configuration file for your system/network environment as needed and save the file. Afterwards, start the Nginx server via "nginx.exe" in the main program directory. Test the availability of the Intrexx portal and of the OData services on the unified address with port 80. Now you should make sure that any embedded URLs in OData documents (such as links to related data records) receive the correct endpoint URL. Normally, the name and port of the OData server is used for the URLs. However, when using a reverse proxy, this differs from the endpoint URL. For this reason, a dynamic mechanism is required that uses the correct URL for OData addresses at runtime. To achieve this, the reverse proxy server sends its host name and port to the back-end system using special request headers (X-Forwarded-*). The back-end system can read the header and use it to create the relevant URL for the clients. To activate this mechanism, use the host parameter in the OData server configuration. Enter either "[X-Forwarded]" here to detect the host based on the request headers, or enter the host name and port of the reverse proxy directly in the format "hostname:port" (which will override the header values).

OData queries in Groovy scripts

Currently, there is no public Intrexx Groovy API for accessing OData in Groovy scripts. It is however possible to enable the internal Intrexx OData API for Groovy. However, this does not guarantee compatibility with future versions of Intrexx and should, therefore, only be used in consultation with your INTREXX customer consultant. To activate access to the internal classes, edit the file scripting.cfg in the portal directory internal/cfg/scripting and add the following lines:

<?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>

The portal service needs to be restarted afterwards. The classes from the packages executed above can now be imported into Groovy.

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

More information

General

System requirements

Consume data

Provide data

Integration in applications

Use in Processes

Expert settings