OData Connector - Appendix
Troubleshooting
Error Messages
OData Provider
If errors occur while processing an OData request, no error details are returned to the client for security reasons. Instead, depending on the type of error, a response is generated with the 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 very beginning of request processing, the only way to analyze it is to embed the detailed error report in the response sent 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." Open the log4j.properties file in the portal directory internal/cfg using a text editor. Search the file for the line "OData Producer," and on the next line, change the value from INFO to DEBUG. After saving the file, the portal server must be restarted to enable the detailed error messages.
OData Consumer
If errors occur during an OData request, Intrexx attempts to identify the error messages from the service's response and display them in the browser. This is not always possible in every case. For a more detailed error analysis, it is therefore a good idea to enable OData request tracing in the Intrexx Portal Server.
Request Tracing and Error Logging
When request tracing is enabled, both OData requests and responses are logged in detail to the Intrexx portal log file. For requests, an entry consists of the HTTP method, the URL, the query parameters, the request headers, and the XML body. For responses, the HTTP headers and the response XML body are output. Tracing is enabled as follows:
-
Open the "log4j2.xml" file in the portal directory `internal/cfg` using a text editor of your choice.
-
Navigate to the "Logging for OData Consumer" section:
<!-- 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 every OData operation, the request details are now logged in the `portal.log` file in the portal's `log` directory.
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 enable detailed logging to 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 for your portal environment, and then restart the portal server after saving the file. The OData requests, including their content, are then logged. Additional settings can be configured in the "logging.properties" file.
Unsupported OData Features
While Intrexx supports all essential features of the OData specification, version 2.0, there may be instances where a service does not support certain features. In this case, either an error occurs or a query does not return the expected result. Since the OData specification allows implementing services a relatively wide range of flexibility regarding feature support, in such a case, only the Intrexx application can be customized so that only features supported by the service are used. Examples of such cases include filter definitions, page navigation (pagination), and sorting. You can disable problematic OData features using the corresponding expert settings. If certain filter definitions are not supported, the filter must be adjusted or simplified accordingly in Intrexx.
SSL connections
For SSL connections between the Intrexx Portal Server and an OData service, the certificate from the certificate authority that issued the service certificate must have been added to the Intrexx Portal Server's certificate store. Self-signed certificates—those not issued by a known certificate authority—are an exception. To enable SSL connections to services using self-signed certificates, you must disable certificate chain validation on the Intrexx Portal Server in this case. This can be done at the service level using a system property. Open the "portal.cfg" file in the portal directory internal/cfg using 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"/>
Replace the placeholder <SERVICE_GUID> with the GUID of the OData service. You can find the GUIDs in the service configuration file in the portal directory `internal/cfg/odata`. After saving the portal.cfg file, you must restart the Intrexx Portal Server service for the changes to take effect.
Ending Intrexx OData Sessions
Intrexx sessions opened via the OData provider are terminated either by an automatic timeout or at the client's request. In the case of timeouts, a distinction is made between anonymous and authenticated sessions. By default, a 60-second timeout is used for anonymous sessions; for user sessions, the session ends automatically after 10 minutes of inactivity. Both timeout options can be configured using a system property. To change the default values, you can add two new <systemProperty> entries to the "environment" section of 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 session timeout values must be specified in milliseconds. The changes will take effect after the portal service is restarted.
The second way to end a session is to send a logout request through the OData client. To do this, the client sends a request to the server; the URL must end with the path $logout. The Intrexx session is identified by the session ID specified as a cookie in the HTTP header and is automatically terminated.
Appendix
OData Specification
You can find a description of the OData protocol and the OData specification here.
OData Tools
The following tools have proven helpful in application development 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 against an OData service using the LINQ query language.
-
Silverlight OData Explorer
Another tool for running and analyzing OData queries in the browser (requires the Microsoft Silverlight plugin), which you can download here.
-
Microsoft Visual Studio 2010
Using Microsoft's Visual Studio 2010 development environment, you can easily generate your own OData services based on existing databases and make them available via Microsoft Internet Information Server. You can download it here.
Reverse Proxy Configuration
For technical reasons, it is currently not possible to run both the portal server and OData services on the same TCP port. Therefore, for Intrexx OData services, you must select a port other than the default HTTP port 80 (or 443 for SSL connections) if you want the portal to be accessible via that port. To run both the portal server and OData services on a single port, it makes sense to use a reverse proxy. This is an upstream web server that receives user requests and forwards them to the appropriate backend system based on a set of rules. There are various open-source and commercial reverse proxy solutions. As an example, the following describes an implementation using the open-source, free web server Nginx. It is assumed that both Nginx and the Intrexx portal server are installed on the same physical server, and that the portal is accessible on port 8080 and the OData services on port 9090. You can download the latest version of Nginx at https://nginx.org. After extracting the download file, the nginx.conf configuration file is located in the /conf subfolder. Replace these with the following sample configuration (a copy of the file is located in the 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;
}
}
}
If necessary, customize the configuration file to match your system or network environment, and then save the file. You can then start the Nginx server using the nginx.exe file in the root directory. Test the availability of the Intrexx portal and the OData services at the standard address on port 80. Now we need to ensure that embedded URLs in OData documents (such as links to related records) contain the correct endpoint URL. Typically, the URL includes the name and port of the OData server. However, when a reverse proxy is used, this differs from the endpoint URL. Therefore, a dynamic mechanism is needed that uses the correct endpoint URL for OData addresses at runtime. To achieve this, the reverse proxy server sends its hostname and port to the backend system via special request headers (X-Forwarded-*). This can read the headers and use them to construct the URL relevant to clients. To enable this mechanism, the "Host" parameter is available in the OData server configuration. Enter either [X-Forwarded] here to determine the host from the request headers, or specify the hostname 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. However, it is possible to enable the internal OData API for Groovy. However, this does not guarantee compatibility with future versions of Intrexx and should therefore only be used after consulting with your Intrexx account manager. To enable access to the internal classes, edit the scripting.cfg file in the portal directory internal/cfg/scripting and add the following lines:
<?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>Next, the Portal Server service must 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();