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:
-
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="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:
-
Postman API Client
Click here for more information.
-
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();