See: Description
Package | Description |
---|---|
com.mathworks.mps.client |
Provides library support for Java clients to communicate with MATLAB Production Server.
|
com.mathworks.mps.client.annotations |
Contains custom annotations for MATLAB Production Server Java client
|
MATLAB Production Server is a client-server framework created by MathWorks for deploying MATLAB code in scalable and robust manner. The server hosts one or more deployable archives, each exporting one or more MATLAB functions. These MATLAB functions can be invoked from a Java application using the Production Server Java client. The client sends request with all the information required to invoke the MATLAB function and receives the result in the response. The server may be running on the same machine as the client or on a remote machine.
The following example demonstrates how MATLAB Production Server can be used to create a scalable MATLAB application.
This example deploys a simple MATLAB function to a MATLAB Production Server instance and creates a Java application that accesses it:
STEP 1: Write a MATLAB function.
STEP 2: Create a deployable archive.
STEP 3: Start a MATLAB Production Server instance.
STEP 4: Deploy the deployable archive to a MATLAB Production Server instance.
STEP 5: Write Java interface for MATLAB function.
STEP 6: Write Java application to call MATLAB function.
NOTE: For the sake of simplicity, we will assume that the client and server are running on the same machine. We will also assume that MATLAB and MATLAB Compiler are installed on this machine.
Create a simple MATLAB function, mymagic
, that takes a single int32
input and returns a magic square as a 2-D double
array:
function m = mymagic(in) m = magic(in); end
Compile the MATLAB function into a deployable archive as follows:
magic
with Deployable Archive
as the target.mymagic
, to this project.magic.ctf
in the magic/for_redistribution_files_only
directory.Note: For this example the MATLAB Production Server is installed in $MPS_INSTALL
directory and
$MPS_INSTALL/script
is on the system path. The directory where we want to create the MATLAB Production Server
instance is /tmp/mpsInst1
.
Note: The paths used in the instructions are for Linux.
Note: Before you can proceed:
You must have installed a MATLAB Runtime instance.
Configured MATLAB Production Server to use it by running mps-setup
.
Before you can start a MATLAB Production Server instance, you must create it:
/tmp/mpsInst1
.
Note : The directory /tmp/mpsInst1
must not exist before entering the following command.
% mps-new /tmp/mpsInst1 %
/tmp/mpsInst1/config/main.config
.
This file has the default configuration parameters when a MATLAB Production Server instance is created for the first time.
Start the server instance:
mps-start
command.
% cd /tmp/mpsInst1 % mps-start %
By default, the MATLAB Production Server instance listens to port number 9910
for client requests.
This can be verified in the /tmp/mpsInst1/endpoint/http
file:
% cat /tmp/mpsInst1/endpoint/http 127.0.0.1:9910
mps-status
command:
% mps-status 'tmp/mpsInst1' STARTED license checked out
Once the MATLAB Production Server instance is started, you need to deploy the deployable archive to it. To deploy the deployable archive, magic.ctf
, copy it to the /tmp/mpsInst1/auto_deploy
folder of the server instance.
Now, it is available to the MATLAB Production Server Java client at URL:
http://localhost:9910/magic
The format of the URL is:
http://<host_name>:<port_number>/<archive_file_name_without_extension>
In order to use the MATLAB function in a Java application, you need to define it using a Java interface. The interface must:
com.mathworks.mps.client.MATLABException
: For reporting MATLAB errorsjava.io.IOException
: For any transport error during client-server communicationThe Java interface for mymagic
will look like following:
interface MatlabMagic { double[][] mymagic(int size) throws IOException, MATLABException; }
The interface name can be any valid Java names.
Note: The MATLAB Production Server Java client API is included in mps_client.jar
. It is located in the
$MPS_INSTALL/client/java
directory where $MPS_INSTALL
is the root directory under
which MATLAB Production Server is installed.
Before you can call the method representing the MATLAB function, you must:
MWHttpClient
to manage connections to MATLAB Production
Server instances.import java.net.URL; import java.io.IOException; import com.mathworks.mps.client.MWClient; import com.mathworks.mps.client.MWHttpClient; import com.mathworks.mps.client.MATLABException; interface MatlabMagic { double[][] mymagic(int size) throws IOException, MATLABException; } public class Magic { public static void main(String[] args){ // Create a non-interruptible MWHttpClient instance MWClient client = new MWHttpClient(); try{ // Create the proxy object that represents magic.ctf MatlabMagic m = client.createProxy(new URL("http://localhost:9910/magic"), MatlabMagic.class ); // The proxy object has mymagic as one of its public methods. Invocation of mymagic // results in a server request that gets the magic square in response double[][] result = m.mymagic(3); // Let's print the magic square printResult(result); }catch(MATLABException ex){ // This exception represents errors in MATLAB and provides useful information // like the MATLAB stack trace or the error ID associated with this error. System.out.println(ex); }catch(IOException ex){ // This exception can represent network issues. It is also thrown when the // HTTP response received from the server has a status of 4XX or 5XX System.out.println(ex); }finally{ // We should close the client when we know that we are not going to need it any more // Once the client is closed, an exception will be thrown if a MATLAB function is // invoked using the proxy object that was created using client. client.close(); } } private static void printResult(double[][] result){ for(double[] row : result){ for(double element : row){ System.out.print(element + " "); } System.out.println(); } } }
MWHttpClient
The following JAR file is required to work with the MATLAB Production Server Java client API:
$MPS_INSTALL/client/java/mps_client.jar
MWHttpClient
A single Java client can connect to one or more servers available at different URL's. Even though users can
create multiple instances of MWHttpClient
, a single instance can be used to establish connections
with multiple servers.
Proxy objects created by using an instance of MWHttpClient
can communicate with MATLAB Production Server
only until the close
method of that instance is invoked. It is important to call the close
method once the MWHttpClient
instance is no more needed to reclaim the system resources.
For a locally scoped instance of MWHttpClient
(e.g a standalone application), the Java client
code will look like following:
MWClient client = new MWHttpClient(); try{ // Code that uses client to communicate with the server }finally{ client.close(); }
If MATLAB Production Server Java client is used in a servlet, MWHttpClient
instance can be tied to the life cycle of the servlet
instance by initializing it in the HttpServlet.init()
method and can be closed in the
HttpServlet.destroy()
method.
public class ExampleServlet extends HttpServlet { private final MWClient client; public void init(ServletConfig config) throws ServletException { client = new MWHttpClient(); } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,java.io.IOException { // Code that uses client to communicate with the server } public void destroy() { client.close(); } }
You configure the client-server connection using an object that implements the MWHttpClientConfig
interface. This interface defines the following properties:
Interruptable
- determines if MATLAB functions can be invoked asynchronouslyTimeOutMs
- determines the amount of time, in milliseconds, the client waits for a response before timing outMaxConnections
- determines the maximum number of connections the client opens to fulfil multiple requestsResponseSizeLimit
- determines the maximum size, in bytes, of the response a client accepts.The API provides a default implementation,MWHttpClientDefaultConfig
, that is automatically used when an HTTP/HTTPS client is instantiated. To modify the configuration, extend MWHttpClientDefaultConfig
and pass it to the HTTP client's constructor.
When you create a client connection using the default constructor, MWHttpClient()
, an instance of MWHttpClientDefaultConfig
is automatically used to configure the client-server connection. The default configuration sets the connection properties as follows:
Interruptable
= false
TimeOutMs
= 120000
MaxConnections
= -1
specifying that the client will use as many connections as allowed by the systemResponseSizeLimit
= 64*1024*1024
(64MB)NOTE : The default connection configuration only allows a blocking call to MATLAB function. For a non-blocking invocation of MATLAB function, please refer to the section Invoke MATLAB Functions Asynchronously.
To change one or more connection properties, use the following steps:
MWHttpClientDefaultConfig
.MWHttpClient(MWHttpClientConfig config)
MWHttpClient(MWHttpClientConfig config, MWSSLConfig sslConfig)
The following code sample creates a client connection with a timeout value of 1000ms:
class MyClientConfig extends MWClientDefaultConfig { public long getTimeOutMs() { return 1000; } } ... MWClient client = new MWHttpClient(new MyClientConfig()); ...
You implement a custom connection configuration by extending MWHttpClientDefaultConfig
. MWHttpClientDefaultConfig
one getter methodf for each configuration property:
public int getMaxConnectionsPerAddress()
- returns the value for the maximum number of connections a client can use to handle simultaneous requests.public long getTimeOutMs()
- returns the number of milliseconds the client will wait for a response before generating an error.public boolean isInterruptible()
- returns a boolean specifying if the MATLAB function can be invoked asynchronously. If this method returns false
, getMaxConnectionsPerAddress()
must return -1
.public int getResponseSizeLimit()
- returns the maximum number of bytes the client can accept in a response.You only need to overrides the getters for properties you wish to change. For example to specify that a client's MATLAB functions can be interrupted you provide an override for isInterruptible()
:
class MyClientConfig extends MWClientDefaultConfig { public boolean isInterruptible() { return true; } }
To specify that a client times out after one second and can only accept 4MB responses you override getTimeOutMs()
and getResponseSizeLimit()
:
class MyClientConfig extends MWClientDefaultConfig { public long getTimeOutMs() { return 60000; } public int getResponseSizeLimit() { return 4*1024*1024; } }
Dynamically invoking MATLAB functions does not require the creation of an interface modelling the contents of a deployable archive. This means that you do not need to recompile your application every time you add a function to a deployed archive.
Dynamic invocation uses a reflection-based proxy to construct the MATLAB function request that is passed to the server instance. When invoking a function, you specify the function name as one of the parameters to the method invoking the request.
To dynamically invoke a MATLAB function:
MWHttpClient
class.createComponentProxy()
methods.invoke()
methods.A reflection-based proxy implements the MWInvokable
interface and provides methods that allow you directly invoke any MATLAB function deployed as part of a deployable archive. Like the interface-based proxy, it is created from the client connection object. The MWHttpClient
class has two methods for creating a reflection-based proxy:
MWInvokable createComponentProxy(URL archiveURL)
- creates a proxy that uses standard MATLAB data typesMWInvokable createComponentProxy(URL archiveURL, MWMarshalingRules marshalingRules)
- creates a proxy that uses structuresTo create a proxy for dynamically invoking functions in the archive myMagic
hosted on your local computer:
MWClient myClient = new MWHttpClient(); URL archiveURL = new URL("http://localhost:9910/myMagic"); MWInvokable myProxy = myClient.createComponentProxy(archiveURL);
A reflection-based proxy has three methods that can be used to invoke functions on a server:
Object[] invoke(final String functionName, final int nargout, final Class<T> targetType, final Object... inputs)
- invoke a function that returns nargout
values<T> T invoke(final String functionName, final Class<T> targetType, final Object... inputs)
- invoke a functions that returns a single valueinvokeVoid(final String functionName, final Object... inputs)
- invoke a function that returns no valuesAll of the methods map to the MATLAB function as follows:
nargout
and targetType
, represent the function's return valuesThe MATLAB function myLimits
returns two values.
function [myMin,myMax] = myLimits(myRange) myMin = min(myRange); myMax = max(myRange); end
To invoke myLimits
from a Java client you use the invoke()
method that takes the number of return arguments:
double[] myRange = new double[]{2,5,7,100,0.5}; try { Object[] myLimits = myProxy.invoke("myLimits", 2, Object[].class, myRange); double myMin = ((Double) myLimits[0]).doubleValue(); double myMax = ((Double) myLimits[1]).doubleValue(); System.out.printf("min: %f max: %f",myMin,myMax); } catch (Throwable e) { e.printStackTrace(); }
Because Java cannot determine the proper types for each of the returned values, this form of invoke
always returns Object[]
and always takes Object[].class
as the target type. You are responsible for casting the returned values into the proper types.
The MATLAB function addmatrix
returns a single value.
function a = addmatrix(a1, a2) a = a1 + a2;
To invoke addmatrix
from a Java client you use the invoke()
method that does not take the number of return arguments:
double[][] a1={{1,2,3},{3,2,1}}; double[][] a2={{4,5,6},{6,5,4}}; try { Double[][] result = myProxy.invoke("addmatrix", Double[][].class, a1, a2); for(Double[] row : result) { for(double element : row) { System.out.print(element + " "); } } } catch (Throwable e) { e.printStackTrace(); }
The MATLAB function foo
does not return value.
function foo(a1) min(a1);
To invoke foo
from a Java client you use the invokeVoid()
method:
double[][] a={{1,2,3},{3,2,1}}; try { myProxy.invokeVoid("foo", (Object)a); } catch (Throwable e) { e.printStackTrace(); }
If any of the MATLAB functions in a deployable archive uses structures, you need to provide marshalling rules to the reflection-based proxy. You provide the marshalling rules to the proxy by:
MWDefaultMarshalingRules
to use a list of the classes being marshalled.createComponentProxy(URL archiveURL, MWMarshalingRules marshallingRules)
. The deployable archive studentChecker
includes functions that use a MATLAB structure of the form
S = name: 'Ed Plum' score: 83 grade: 'B+'
Your Java client code represents the MATLAB structure with a class named Student
. To create a marshalling rule for dynamically invoking the functions in studentChecker
, you create a class named studentMarshaller
.
class studentMarshaller extends MWDefaultMarshalingRules { public List<Class> getStructTypes() { List structType = new ArrayList<Class>(); structType.add(Student.class); return structType; } }
You create the reflection-based proxy for studentChecker
by passing studentMarshaller
to createComponentProxy()
.
URL archiveURL = new URL("http://localhost:9910/studentCheck"); myProxy = myClient.createComponentProxy(archiveURL, new StudentMarshaller());
For more information about using MATLAB structures see Marshalling MATLAB structures (or structs).
MATLAB functions can be invoked asynchronously using the invokeAsync
method provided by the MWInvokable
interface. As can be seen in the example later, the asynchronous feature uses the approach of dynamic invocation of MATLAB functions only and not the interface based approach. The invokeAsync
method is a non-blocking method that fires the MATLAB execution request and returns the control back to the client application. This is particularly useful for long running MATLAB functions. There are 2 ways to get to the response of MATLAB function execution request:
MWRequest
instance returned by invokeAsync
provides access to java.util.concurrent.Future
using the getFuture
method. Client application can invoke the get
method provided by java.util.concurrent.Future
to get the MATLAB result. The call to get
is a blocking call and will wait for MATLAB to finish execution if it is not already done. Once MATLAB result is received, it is cached and future calls to the get
method will result the cached response.import java.net.URL; import java.util.concurrent.Future; import com.mathworks.mps.client.*; class MyConfig extends MWHttpClientDefaultConfig{ public boolean isInterruptible() { return true; } public int getMaxConnectionsPerAddress() { return 10; } } public class MagicAsync{ public static void main(String[] args){ MWClient client = new MWHttpClient( new MyConfig() ); try{ MWInvokable invokable = client.createComponentProxy(new URL("http://localhost:9910/magic")); MWInvokeRequesthttpRequest = new MWInvokeRequest ("mymagic", double[][].class); httpRequest.setInputParams(4); httpRequest.setNargout(1); MWRequest request = invokable.invokeAsync(httpRequest, null); Future f = request.getFuture(); double[][] res = f.get(); printResult(res); }catch(Exception ex){ System.out.println(ex); } finally{ client.close(); } } private static void printResult(double[][] result){ for(double[] row : result){ for(double element : row){ System.out.print(element + " "); } System.out.println(); } } }
MWRequestListener
instance passed as an input to invokeAsync
. This way, they will be notified of various state changes that a request goes through during its life time. However, it should be noted that the client is not guaranteed to receive notification of every state depending on how busy the server is. It is possible that request is ready with response before client gets a chance to ask for status of intermediate states.import java.net.URL; import com.mathworks.mps.client.*; class MyClientConfig extends MWHttpClientDefaultConfig{ public boolean isInterruptible() { return true; } public int getMaxConnectionsPerAddress() { return 10; } } class ReqListenerimplements MWRequestListener { public ReqStateVisitor visitor; public ReqListener(ReqStateVisitor visitor) { this.visitor = visitor; } public void notify(MWRequest request) { MWRequestState state = request.getState(); state.visit(visitor); } } class ReqStateVisitor implements MWRequestStateVisitor { private MWClient client; public ReqStateVisitor(MWClient client){ this.client = client; } public void cancelled() { } public void expired() { } public void failed(Exception ex) { } public void interrupted() { } public void inQueue(long timeStamp, URL requestURL) { } public void processing(long timestamp, URL requestURL) { } public void sending(byte[] data, URL serviceURL) { } public void ready(T responseData) { printResult( (double[][])responseData ); client.close(); } public void printResult(double[][] result){ for(double[] row : result){ for(double element : row){ System.out.print(element + " "); } System.out.println(); } } } public class MagicAsyncWithListener{ public static void main(String[] args){ MWClient client = new MWHttpClient(new MyClientConfig()); ReqStateVisitor v = new ReqStateVisitor (client); ReqListener listener = new ReqListener (v); try{ MWInvokable invokable = client.createComponentProxy(new URL("http://localhost:9910/magic")); MWInvokeRequest httpRequest = new MWInvokeRequest ("mymagic", double[][].class); httpRequest.setInputParams(4); httpRequest.setNargout(1); invokable.invokeAsync(httpRequest, listener); }catch(Exception ex){ System.out.println(ex); } } }
NOTE : invokeAsync
is only usable when the MWHttpClient
instance is created using an interruptible MWHttpClientConfig
. Users can either implement the MWHttpClientConfig
interface or extend the MWHttpClientDefaultConfig
class to override the isInterruptible
method so that it returns true.
MATLAB Production Server Java client application has to handle following checked exceptions:
com.mathworks.mps.client.MATLABException
: This represents a MATLAB error. It is thrown if
there is an error in MATLAB when a proxy object method is executed. Following MATLAB information can be obtained
from an instance of this class :
java.io.IOException
: This is thrown if there are either any network related failures or if the
server returns with a response status of either 4XX or 5XX.
com.mathworks.mps.client.MWHttpException
,
a subclass of java.io.IOException
is also available if one needs to handle response status of 4XX or
5XX in a special manner.Connecting to a MATLAB Production Server instance over HTTPS provides a secure channel for executing MATLAB functions. To establish an HTTPS connection with a MATLAB Production Server instance:
https://
URL.MATLAB Production Server's Java client API also provides:
HostnameVerifier
Use keytool
to manage the key store and trust stores on the client machine. At a minimum the client requires the server's root CA (Certificate Authority) in its truststore.
If the client needs to connect to a server that requires client-side authentication, it also needs a signed certificate in its key store.
For information on using keytool
see Oracle's keytool
documentation.
You can create a secure proxy connection with a MATLAB Production Server instance by using the https://
URL for the desired program:
MWClient client = new MWHttpClient(); URL sslURL = new URL("https://hostname:port/myProgram"); MyProxy sslProxy = client.createProxy(sslURL, MyProxy.class);
sslProxy
will use the default Java trust store, stored in JAVA_HOME\lib\security\cacerts
, to perform the HTTPS server authentication. If the server requests client authentication, the HTTPS handshake will fail because the default SSLContext
object created by the JRE does not provide a key store.
To enable your client to connect with a server instance requiring client authentication, you set the key store location and password using Java system properties:
System.setProperty("javax.net.ssl.keystore", "PATH_TO_KEYSTORE"); System.setProperty("javax.net.ssl.keystorePassword", "keystore_pass"); MWClient client = new MWHttpClient(); URL sslURL = new URL("https://hostname:port/myfun"); MyProxy sslProxy = client.createProxy(sslURL, MyProxy.class);
To use a non-default location for the client trust store, you set the trust store location and password using Java system properties:
System.setProperty("javax.net.ssl.truststore", "PATH_TO_TRUSTSTORE"); System.setProperty("javax.net.ssl.truststorePassword", "truststore_pass"); MWClient client = new MWHttpClient(); URL sslURL = new URL("https://hostname:port/myfun"); MyProxy sslProxy = client.createProxy(sslURL, MyProxy.class);
To use a custom SSLContext
implementation, add a custom HostnameVerifier
implementation, or use the MATLAB Production Server Java API's server authorization, you must provide a custom implementation of MWSSLConfig
.
The Java API uses a MWSSLConfig
object to get the information it needs to use HTTPS and perform the additional server authorization. The MWSSLConfig
interface has three methods:
getSSLContext()
- returns the SSLContext
objectgetHostnameVerifier()
- returns the HostnameVerifier
object to use if HTTPS hostname verification failsgetServerAuthorizer()
- returns the MWSSLServerAuthorizer
object to perform server authorization based on the server's certificateThe Java API provides a default MWSSLConfig
implementation, MWSSLDefaultConfig
, which it uses when no SSL configuration is passed to the MWHTTPClient
constructor. The MWSSLDefaultConfig
is implemented such that:
getSSLContext()
returns the default SSLContext
object created by the JRE.getHostnameVerifier()
returns a HostnameVerifier
implementation that always returns false
. If the HTTPS hostname verification fails, this will not override the HTTPS layer's decision.getServerAuthorizer()
returns a MWSSLServerAuthorizer
implementation that authorizes all MATLAB Production Server instances.As part of the SSL handshake, the HTTPS layer attempts to match the hostname in the provided URL to the hostname provided in the server's certificate. If the two hostnames do not match, the HTTPS layer calls HostnameVerifier.verify()
as an additional check. The return value of HostnameVerifier.verify()
determines if the hostname is verified.
The implementation of HostnameVerifier.verify()
provided by the MWSSLDefaultConfig
object always returns false
. The result is that if the hostname in the URL and the hostname in the server certificate do not match, the HTTPS handshake fails.
To use a hostname verification scheme that is more robust, you can extend the MWSSLDefaultConfig
class to return a version of HostnameVerifier.verify()
that uses custom logic. For example, if you only wanted to generate one certificate for all of the servers on which MATLAB Production Server instances run, you could specify MPS
for the certificate's hostname. Then your implementation of HostnameVerifier.verify()
returns true
if the certificate's hostname is MPS
.
public class MySSLConfig extends MWSSLDefaultConfig { public HostnameVerifier getHostnameVerifier() { return new HostNameVerifier() { public boolean verify(String s, SSLSession sslSession) { if (sslSession.getPeerHost().equals("MPS")) return true; else return false; } } } }
For more information on HostnameVerify
see Oracle's Java Documentation.
After the HTTPS layer establishes a secure connection, a client can perform an additional authentication step before sending requests to a server. This additional authentication os performed by an implementation of the MWSSLServerAuthorizer
interface. A MWSSLSServerAuthorizer
implementation performs two checks to authorize a server:
isCertificateRequired()
determines if server's must present a certificate for authorization. If this returns true
and the server has not provided a certificate, the client does not authorize the server.authorize(Certificate serverCert)
uses the server's certificate to determine if the client authorizes the server to process requests.The MWSSLSServerAuthorizer
implementation returned by MWSSLDefaultConfig
authorizes all servers without performing any checks.
To use server authentication extend MWSSLDefaultConfig
and override the implementation of getServerAuthorizer()
to return a MWSSLSServerAuthorizer
implementation that does perform authorization checks.
MATLAB allows users to write functions with multiple outputs. Java does not support methods with multiple outputs.
Following is a simple MATLAB function signature with 2 outputs and 2 inputs. Let's assume that the first input is
of type double
and the second is a char
array and for the sake of simplicity, let's assume
the same types for the first and the second output of this function:
function [out1 out2] = multipleOutputs(in1, in2)
The Java method signature for such a MATLAB function for MATLAB Production Server Java client must include the number of output arguments as
the first input, of type int
, followed by the remaining inputs. The return type of this Java method must be
Object[]
. Thus the Java method signature for MATLAB function multipleOutputs
will look like :
public Object[] multipleOutputs(int nargout, double in1, String in2);
The actual invocation of this method from the Java application may look something like:
Object[] result = mycomp.multipleOutputs(2, 1.2, "test");
The output result
will be an array of length 2 with the first element of Java type double
and the second element of Java type char
.
varargin
and varargout
MATLAB Production Server Java client supports MATLAB's variable number of inputs,
varargin, and outputs,
varargout. Following is a
MATLAB function signature that uses varargin
and varargout
:
function varargout = vararginout(in1, in2, varargin)
Let's assume that the above MATLAB function has double
as the type for first input and char
array as the type for second input. The type of data being passed as part of varargin
can be any supported type.
Let's assume that this function returns 2 outputs as varargout
with types double
and
char
.
The Java method signature for MATLAB function vararginout
will look almost similar to the one discussed
in the previous section. The Java method signature supported by MATLAB Production Server Java client for this MATLAB function will
look like :
public Object[] vararginout(int nargout, double in1, String in2, Object... vararg);
The actual invocation of this method from the Java application may look something like:
Object[] result = mycomp.vararginout(2, 1.2, "test", true, "another string");or
Object[] result = mycomp.vararginout(2, 1.2, "test", new Object[]{true, "another string"});
null
When MATLAB Production Server Java client invokes a MATLAB function through a request and receives result in the response, data conversion takes place between MATLAB and Java data types.
There are many different data types, or classes, that you can work with in MATLAB. You can build matrices and arrays of floating-point and integer data, characters and strings, and logical true and false states. Function handles connect your code with any MATLAB function regardless of the current scope. Structures and cell arrays, provide a way to store dissimilar types of data in the same array.
There are 15 fundamental classes in MATLAB. Each of these classes is in the form of a matrix or array. With the exception of function handles, this matrix or array is a minimum of 0-by-0 in size and can grow to an n-dimensional array of any size. A function handle is always scalar (1-by-1). All of the fundamental MATLAB classes are circled in the diagram below:
A structure consists of data containers, called fields, and each of these fields stores an array of some MATLAB data type. Every field has a unique name. A field in a structure can have a value of any of the MATLAB data types, including a cell array or another structure.
A cell array is a collection of containers called cells in which you can store different types of MATLAB data including other cell arrays and structures.
In MATLAB, dimensionality is an attribute of the fundamental types and does not add to the number of types as it
does in Java. In Java, double, double[]
and double[][][]
are three different data types.
Where as in MATLAB, there is just double
data type and there can be a scalar instance, a vector
instance or a multi dimensional instance of this type.
Also, numeric classes in MATLAB include signed and unsigned integers. Java does not have unsigned types.
The table below shows the data marshalling rules applied when data is passed from Java to MATLAB.
Values passed to Java method | Input type received by MATLAB | Dimension of data in MATLAB |
---|---|---|
java.lang.Byte, byte | int8 | {1,1} |
byte[] data | {1, data.length} | |
java.lang.Short, short | int16 | {1,1} |
short[] data | {1, data.length} | |
java.lang.Integer, int | int32 | {1,1} |
int[] data | {1, data.length} | |
java.lang.Long, long | int64 | {1,1} |
long[] data | {1, data.length} | |
java.lang.Float, float | single | {1,1} |
float[] data | {1, data.length} | |
java.lang.Double, double | double | {1,1} |
double[] data | {1, data.length} | |
java.lang.Boolean, boolean | logical | {1,1} |
boolean[] data | {1, data.length} | |
java.lang.Character, char | char | {1,1} |
char[] data | {1, data.length} | |
java.lang.String data | {1, data.length()} | |
java.lang.String[] data | cell | { 1, data.length} |
java.lang.Object[] data | { 1, data.length} | |
In addition, for any convertible data type T above, the following Java type is also a convertible data type | ||
T[] data | MATLAB type for T | { data.length, dimensions(T[0]) }, if T is an Array |
{ 1, data.length }, if T is not an Array | ||
NOTE : If T is an array type, then all elements of data must have exactly the same length |
The table below shows the data marshalling rules applied when data is passed from MATLAB to Java.
NOTE: Since Java does not have unsigned numeric types, MATLAB unsigned types are converted to the appropriate signed types in Java as shown in the following table. Also, data marshalling for MATLAB structures is discussed separately in its own section.
What MATLAB returns | Dimension of data in MATLAB | Java type that MATLAB data will be converted to |
---|---|---|
int8, unit8 | {1,1} | byte, java.lang.Byte |
{1,n} | byte[n], java.lang.Byte[n] | |
{m,n,p,...} | byte[m][n][p]... , java.lang.Byte[m][n][p]... | |
int16, unit16 | {1,1} | short, java.lang.Short |
{1,n} | short[n], java.lang.Short[n] | |
{m,n,p,...} | short[m][n][p]... , java.lang.Short[m][n][p]... | |
int32, unit32 | {1,1} | int, java.lang.Integer |
{1,n} | int[n], java.lang.Integer[n] | |
{m,n,p,...} | int[m][n][p]... , java.lang.Integer[m][n][p]... | |
int64, unit64 | {1,1} | long, java.lang.Long |
{1,n} | long[n], java.lang.Long[n] | |
{m,n,p,...} | long[m][n][p]... , java.lang.Long[m][n][p]... | |
single | {1,1} | float, java.lang.Float |
{1,n} | float[n], java.lang.Float[n] | |
{m,n,p,...} | float[m][n][p]... , java.lang.Float[m][n][p]... | |
double | {1,1} | double, java.lang.Double |
{1,n} | double[n], java.lang.Double[n] | |
{m,n,p,...} | double[m][n][p]... , java.lang.Double[m][n][p]... | |
logical | {1,1} | boolean, java.lang.Boolean |
{1,n} | boolean[n], java.lang.Boolean[n] | |
{m,n,p,...} | boolean[m][n][p]... , java.lang.Boolean[m][n][p]... | |
char | {1,1} | char, java.lang.Character |
{1,n} | java.lang.String | |
{m,n,p,...} | char[m][n][p]... , java.lang.Character[m][n][p]... | |
cell (containing only strings) | {1,1} | java.lang.String |
{1,n} | java.lang.String[n] | |
{m,n,p,...} | java.lang.String[m][n][p]... | |
cell (containing multiple types) | {1,1} | java.lang.Object |
{1,n} | java.lang.Object[n] | |
{m,n,p,...} | java.lang.Object[m][n][p]... |
Following data types in MATLAB are not supported for marshalling by MATLAB Production Server Java client:
With type coercion feature provided by MATLAB Production Server Java client, numeric MATLAB types can be assigned to
multiple Java numeric types as long as there is no loss of data or precision. The main exception to this rule is the coercion
of MATLAB double
data into any Java numeric types. This is allowed because double
is the
default numeric type in MATLAB and this exception to the coercion rule provides more flexibility to the users
of MATLAB Production Server. Following table describes the type compatibility for scalar and non-scalar numeric coercion.
MATLAB types | Compatible Java types supported by coercion |
---|---|
uint8 | short, int, long, float, double |
int8 | short, int, long, float, double |
uint16 | int, long, float, double |
int16 | int, long, float, double |
uint32 | long, float, double |
int32 | long, float, double |
uint64 | float, double |
int64 | float, double |
single | double |
double | byte, short, int, long, float |
NOTE: As non-scalar numeric coercion may impose a performance penalty, especially for large arrays, it is recommended to keep the Java return types consistent with MATLAB function.
MATLAB Production Server Java client provides some flexibility in how data is received from MATLAB. This flexibility is in the form of the dimensionality of the target Java type corresponding to a result received from MATLAB i.e it is possible for the target Java type to have dimensions either less than or more than the dimensions of data received from MATLAB.
Consider following simple MATLAB function that returns an array of type double
with values from 1 to 10.
function out = returnData out = 1:10;
In MATLAB, out
is of type double
with dimensions 1x10 (number_of_dimensions = 2).
MATLAB Production Server Java client allows a Java programmer to look at out
as any of the following:
double[10]
(number_of_dimensions = 1)double[1][10]
(number_of_dimensions = 2)double[1][10][1]
(number_of_dimensions = 3)double[1][10][1][1]..[1]
(number_of_dimensions = N)With MATLAB Production Server Java client, MATLAB function returnData
can be represented in Java as any one of the following methods:
double[] returnData() throws MATLABException, IOException; double[][] returnData() throws MATLABException, IOException; double[][][] returnData() throws MATLABException, IOException; double[][][][] returnData() throws MATLABException, IOException; ... ...
Thus, on top of the default behaviour of preserving the number of dimensions of data received from MATLAB, MATLAB Production Server Java client allows dimension coercion in the form of following :
When Java method's return type has more number of dimensions than MATLAB response, MATLAB's dimensions array will be padded with 1's to match the required number of output dimensions in Java.
Here's another example for more clarity. Consider following MATLAB function:
function a = foo a = ones(2,3);If following Java method was used to represent this MATLAB function,
double[][][][] foo() throws MATLABException, IOException;the dimensions of data received in Java will be {2,3,1,1}.
This is the case when Java method's return type has fewer number of dimensions than data in MATLAB. This case is possible only when extra dimensions for MATLAB array have value of 1 only. To compute number of dimensions in Java, excess 1's are removed first from the right in the dimensions array and then from the left.
For more clarity, consider following MATLAB function:
function a = foo a = ones(1,2,1,1,3,1);If following Java method was used to represent this MATLAB function,
double[][] foo() throws MATLABException, IOException;the dimensions of data received in Java will be {2,3}.
Here are some more examples of dimension narrowing:
MATLAB Array Dimensions | Declared Output Java Type | Output Java Dimensions |
---|---|---|
1x1 | double | 0 |
2x1 | double[] | 2 |
1x2 | double[] | 2 |
2x3x1 | double[][] | 2x3 |
1x3x4 | double[][] | 3x4 |
1x3x4x1x1 | double[][][] | 1x3x4 |
1x3x1x1x2x1x4x1 | double[][][][] | 3x2x1x4 |
null
An empty array in MATLAB has at least one 0 as one of the dimensions. Following are a few examples of MATLAB expressions which create empty data:
empty_double = []; empty_double_array = ones(0,0); empty_char_array = ''; empty_cell_array = {}; empty_int32_array = int32( ones(1,2,0,3) ); empty_struct = struct([]);
Empty MATLAB data will be returned to Java as null
for all the cases except when the target type in Java
is a scalar primitive. In Java, primitive scalar types cannot be assigned value of null
and a Java exception
will be thrown by MATLAB Production Server Java client.
Also, empty MATLAB char
array will be marshalled into Java as an empty string only if the target type in Java is java.lang.String
.
If the target type is not java.lang.String
, it will be marshalled as null
.
null
-> MATLAB
When a null value is passed from Java to MATLAB, it will always be marshalled into []
i.e a
0x0 double
array in MATLAB. This will be independent of the declared input type used in Java.
In Java, following methods can accept null
as input value:
void foo(String input); void foo(double[] input); void foo(double[][] input); void foo(Double input);In MATLAB, for all the above methods,
null
will be received as:
[] i.e. 0x0 double
MATLAB Production Server Java client will do the primitive to boxed type conversion if users have used boxed types as return types in the Java method signature. E.g following Java method signatures will work interchangeably:
double[] foo(); <-> Double[] foo(); double[][][] foo(); <-> Double[][][] foo();
Structures are MATLAB arrays with elements accessed by textual field designators. Following is an example of how structures are created in MATLAB:
S.name = 'Ed Plum'; S.score = 83; S.grade = 'B+'creates a scalar structure with three fields:
S = name: 'Ed Plum' score: 83 grade: 'B+'A multi dimensional structure array can be created in MATLAB by inserting additional elements:
S(2).name = 'Toni Miller'; S(2).score = 91; S(2).grade = 'A-';creates a structure array of dimensions {1,2}. One can also create structure arrays with more than 2 dimensions.
Since Java does not have built-in support for structure data type, marshalling MATLAB structures between MATLAB Production Server server and client involves extra work.
Following is a simple example of how MATLAB structure can be marshalled between MATLAB Production Server Java client and server
Let's consider a MATLAB function sortstudents
that takes in an array of structures described above.
Each element in this array is representing information about a student. This function sorts the input array in
the ascending order by score
of each student
function sorted = sortstudents(unsorted) % Receive a vector of students as input % Get scores of all the students scores = {unsorted.score}; % Convert the cell array containing scores into a numeric array or doubles scores = cell2mat(scores); % Sort the scores array [s i] = sort(scores); % Sort the students array based on the sorted scores array sorted = unsorted(i);
NOTE: Even though sortstudents
is only using score
field of the input structure,
name
and grade
fields are also available and can be accessed
Let's assume that this MATLAB function is compiled into scoresorter.ctf
and is now available
for Java MATLAB Production Server client at URL: http://localhost:9910/scoresorter
Before defining the Java interface required by the MATLAB Production Server Java client, we need to define MATLAB structure, student
,
in Java. This will be achieved by a Java class Student
with fields name, score
and
grade
with appropriate types. It will also have public get
and set
functions
to access these fields.
public class Student{ private String name; private int score; private String grade; public Student(){ } public Student(String name, int score, String grade){ this.name = name; this.score = score; this.grade = grade; } public String getName(){ return name; } public void setName(String name){ this.name = name; } public int getScore(){ return score; } public void setScore(int score){ this.score = score; } public String getGrade(){ return grade; } public void setGrade(String grade){ this.grade = grade; } public String toString(){ return "Student:\n\tname : " + name + "\n\tscore : " + score + "\n\tgrade : " + grade; } }
NOTE: toString
method is provided as a convenience method. It is not required for marshalling
Let's define Java interface StudentSorter
that has method sortstudents
and uses
Student
class for inputs and outputs. Student
must be included in the
MWStructureList
annotation.
interface StudentSorter { @MWStructureList({Student.class}) Student[] sortstudents(Student[] students) throws IOException, MATLABException; }
The final Java application using MATLAB Production Server Java client will look like following:
import java.net.URL; import java.io.IOException; import com.mathworks.mps.client.MWClient; import com.mathworks.mps.client.MWHttpClient; import com.mathworks.mps.client.MATLABException; import com.mathworks.mps.client.annotations.MWStructureList; interface StudentSorter { @MWStructureList({Student.class}) Student[] sortstudents(Student[] students) throws IOException, MATLABException; } public class ClientExample { public static void main(String[] args){ MWClient client = new MWHttpClient(); try{ StudentSorter s = client.createProxy(new URL("http://localhost:9910/scoresorter"), StudentSorter.class ); Student[] students = new Student[]{new Student("Toni Miller", 90, "A"), new Student("Ed Plum", 80, "B+"), new Student("Mark Jones", 85, "A-")}; Student[] sorted = s.sortstudents(students); System.out.println("Student list sorted in the ascending order of scores : "); for(Student st:sorted){ System.out.println(st); } }catch(IOException ex){ System.out.println(ex); }catch(MATLABException ex){ System.out.println(ex); }finally{ client.close(); } } }
A structure in MATLAB is an ordered list of name-value pairs. It can be represented in Java as a class with fields
with the same case-sensitive names. This class also needs to have public get
and/or set
methods for
each of these fields as shown in the Student
class above. Whether or not the class needs both get
and set
methods for the fields depends on whether it is being used as input or output or both.
Java classes that represent MATLAB structures use the Java Beans Introspector (http://docs.oracle.com/javase/6/docs/api/java/beans/Introspector.html) to map properties to fields and its default naming conventions are used.
This means that by default its decapitalize (http://docs.oracle.com/javase/6/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String) method is used. This maps the first letter of a Java field into a lower-case letter. By default, it is not possible to define a Java field which will map to a MATLAB field which starts with an upper case.
You can override this behaviour by implementing a
import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.beans.SimpleBeanInfo; public class StudentBeanInfo extends SimpleBeanInfo { @Override public PropertyDescriptor[] getPropertyDescriptors() { PropertyDescriptor[] props = new PropertyDescriptor[3]; try { // name uses default naming conventions so we do not need to explicitly specify the accessor names. props[0] = new PropertyDescriptor("name",MyStruct.class); // score uses default naming conventions so we do not need to explicitly specify the accessor names. props[1] = new PropertyDescriptor("score",MyStruct.class); // Grade uses a custom naming convention so we do need to explicitly specify the accessor names. props[1] = new PropertyDescriptor("Grade",MyStruct.class,"getGrade","setGrade"); return props; } catch (IntrospectionException e) { e.printStackTrace(); } return null; } }
When Student
is passed as an input to method sortstudents
, only the get
methods for
its fields are used by the data marshalling algorithm. As a result, if a Java class is being defined for a MATLAB structure
that is only used as an input value, the set
methods are not required. Thus a shorter version of Student
class that is only used to represent input values will look like following:
public class Student{ private String name; private int score; private String grade; public Student(String name, int score, String grade){ this.name = name; this.score = score; this.grade = grade; } public String getName(){ return name; } public int getScore(){ return score; } public String getGrade(){ return grade; } }
When Student
is used as output only, the data marshalling algorithm needs to create new instances of it using the
structure received from MATLAB. This can be achieved using the set
methods or @ConstructorProperties
annotation provided by Java. get
methods are not required for a Java class defining output only MATLAB
structure
set
methodsAn output only Student
class using set
methods will look like following:
public class Student{ private String name; private int score; private String grade; public void setName(String name){ this.name = name; } public void setScore(int score){ this.score = score; } public void setGrade(String grade){ this.grade = grade; } }
@ConstructorProperties
annotationAn output only Student
class using @ConstructorProperties
will look like following:
import java.beans.ConstructorProperties; public class Student{ private String name; private int score; private String grade; @ConstructorProperties({"name","score","grade"}) public Student(String n, int s, String g){ this.name = n; this.score = s; this.grade = g; } }
set
methods and @ConstructorProperties
annotation are provided, set
methods get
precedence over @ConstructorProperties
annotation.If Student
is used as both an input and output, one needs to provide get
methods to take care of
marshalling to MATLAB. For marshalling from MATLAB, one can either use set
methods or @ConstructorProperties
annotation.
Copyright 2010-2016 The MathWorks, Inc.