Connector for OData
Troubleshooting
Error messages
OData provider
If errors occur during OData request processing, no error details are returned to the client for security reasons. Instead, depending on the type of error, a reply is generated with a corresponding HTTP error code. If the error occurs within the business logic, the portal server log may contain detailed error messages. However, if the error occurs at the start of request processing, the only option for analysis is to embed the detailed error report in the response to the client. To do this, the Log4j configuration of the Intrexx portal server must be adjusted so that "DEBUG" is used as the log level for the OData producer module instead of "INFO". To do this, open the ""log4j.properties"" file in the portal directory \internal\cfg\ in a text editor. Search for the line "OData Producer" in the file and change the value INFO to DEBUG in the next line. 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 determine the error messages from the response of the service and 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, an 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:
-
Open the file "log4j2.xml" in the portal directory internal/cfg with a text editor of your choice.
-
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"/>
-
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"/>
-
Restart the portal service.
-
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: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>
To activate detailed logging in a log file for the OData provider, open the "portal.wcf" file in the portal directory internal/cfg 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> and <portal> with the values of your portal environment and restart the portal server after saving the file. Afterwards, the OData requests will be logged along with their contents. Further settings can be made 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 offers the implementing services a relatively large scope in terms of feature support, in such a case only the Intrexx application can be adapted so that only functions supported by the service are used. Examples of such cases are filter definitions, site navigation (pagination) or sorting. Problematic OData features can be deactivated via the corresponding expert settings. If certain filter definitions are not supported, the filter in Intrexx must be adapted or simplified accordingly.
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. 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 placeholder <SERVICE_GUID> must be replaced with the GUID of the OData service. 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, two new <systemProperty> entries can be added to the "environment" section in the "portal.cfg" file in the portal directory internal/cfg:
<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 and 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:
-
Postman API Client
You can find all the information here.
-
LinqPad
With LinqPad, which you can download here, you can execute and visualize queries on an OData service using the Linq query language.
-
Silverlight OData Explorer
Another tool for executing and analyzing OData queries 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 them 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. Therefore, a port other than the standard HTTP port 80 (or 443 for SSL connections) must be selected for Intrexx OData services if the portal is to be accessible under this 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 obtain the current Nginx version at 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 the mechanism, there is the "Host" parameter in the OData server configuration. Either enter [X-Forwarded] here to determine the host from the request headers, or enter the host name and port directly in the format hostname:port of the reverse proxy (which overwrites header values).
OData queries in Groovy scripts
There is currently 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 Intrexx versions and should therefore only be used after consultation with your (Undefined variable: General.CompanyName-without-GMBH) customer advisor. To activate access to the internal classes, edit the scripting.cfg file in the portal directory internal/cfg/scripting and enter the lines below:
<?xml version="1.0" encoding="UTF-8"?>
<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 server service must then be restarted. The classes from the packages listed 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();