고급 사용자 정의 > Info*Engine 사용자 안내서 > SOAP Services > Example Standalone Java SOAP Client
  
Example Standalone Java SOAP Client
Standalone Java SOAP clients can use the Java EE connector to interact with Info*Engine. They do not, however, provide the added value that an application server can bring to your connections.
Writing a standalone Java SOAP client requires the following steps:
1. Decide on the classes and methods required by your application.
2. Implement the required tasks inside of a directory hierarchy that reflects the required classes.
In our example this includes the average.xml and sum.xml tasks in the org/myOrg/Math subdirectory of the task root. These are described in the “Implementing Tasks and Java Packages” section below..
3. Create the required type identifiers and delegates in the LDAP directory. This is discussed in the “Registering Delegates” section below.
4. Generate Data Access Objects (DAOs) for each class from Step 1. DAOs are discussed in the “Generating DAOs” section below.
5. Decide how connections are to be created, and if necessary bind connection factory objects in the LDAP directory. This is described in the “Managing Connection Factories in the LDAP Directory” section below.
6. Write client source code that uses the DAOs from Step 4 to access Info*Engine. This is described in the “Putting It All Together” section below.
The following sections use a simple example application to demonstrate how a standalone Java SOAP client should communicate with Info*Engine.
Before You Begin Writing the Standalone Java SOAP Client
Before you begin writing your standalone Java SOAP client, you must do one of the following:
Ensure that the following JAR files are in your CLASSPATH:
$(<Windchill>)/lib/servlet.jar
$(<Windchill>)/codebase/WEB-INF/lib/ieWeb.jar
$(<Windchill>)/codebase/WEB-INF/lib/ie3rdpartylibs.jar
where <Windchill> is the Info*Engine installation directory.
or
Extract the contents of $(<Windchill>)/ieconnector/ie.rar into a directory and add all JAR files found there to your CLASSPATH.
Assumed Knowledge
Before implementing an Info*Engine SOAP client, it is assumed that you have the following knowledge:
You should be familiar with the Info*Engine SOAP RPC servlet and writing tasks for use with SOAP.
You should have a solid working knowledge of writing Java applications. These sections do not cover fundamentals such as compiling example source code or setting the CLASSPATH.
Implementing Tasks and Java Classes
For our example, the sum.xml and average.xml tasks are exposed to a standalone SOAP client using the “org.myOrg.Math” class.
* 
For ease of instruction, the tasks used in this sample application are supplied only as examples, and do not represent the types of activities for which Info*Engine tasks are typically written to accomplish.
sum.xml
The sum.xml task takes two integers and returns their sum. The following code is the contents of /org/myOrg.Math/sum.xml:
<%@page language="java"%>
<%@ taglib uri="http://www.ptc.com/infoengine/tag
lib/core"prefix="ie"%>

<!--com.infoengine.soap.rpc.def
this task takes two integers and adds them together
@param int x
@param int y

@return int $(output[]sum[])
-->
<%
Integer x = (Integer)getParam ( "x" );
Integer y = (Integer)getParam ( "y" );
String element = "sum=" + (x.intValue()+y.intValue());
%>

<ie:webject name="Create-Group" type="GRP">
<ie:param name="ELEMENT" data="<%=element%>"/>
<ie:param name="GROUP_OUT" data="output"/>
<ie:webject>
average.xml
The average.xml task takes an array of numbers and returns their average. The following code is the contents of /org/myOrg/Math/average.xml:
<%@page language="java"%>
<%@ taglib uri="http://www.ptc.com/infoengine/tag
lib/core"prefix="ie"%>

<!--com.infoengine.soap.rpc.def
this task takes an array of numbers and averages
them

@param double[]nums

@return double $(output[]avg[])
-->
<%
java.util.Vector nums = getParams ( "nums" );
double sum = 0;
for ( int i = 0; i < nums.size(); i++ )
sum += ((Double)nums.elementAt(i)).doubleValue();
String element = "avg=" + (sum/(double)nums.size());

%><ie:webject name="Create-Group" type="GRP">
<ie:param name="ELEMENT" data="<%=element%>"/>
<ie:param name="GROUP_OUT" data="output"/>
<ie:webject>
Registering Delegates
For any SOAP client to invoke an Info*Engine task, the task must be registered as a delegate in the LDAP directory.
The tasks from our example would be registered using the type identifier of “org.myOrg.Math.” This type identifier would contain two delegates: sum and average.
For more information on registering tasks as delegates, see the online help available from the Task Delegate Administration utility.
Generating DAOs
Data Access Objects (DAOs) are generated from Info*Engine tasks. Each DAO exposes one method signature per exposed Info*Engine task.
A DAO generated from the “org.myOrg.Math” class would expose the following public method signatures:
public int sum ( int x, int y ) throws Exception;
public double average ( double [] nums ) throws Exception;
And the following constructor (assuming a class name of “MathDAO”):
public MathDAO ( javax.resource.cci.Connection c, javax.
resource.cci.RecordFactory r );
Create the required instances of javax.resource.cci.Connection and javax.resource.cci.RecordFactory.
Generated DAO methods may throw a java.lang.Exception. The reason such a generic exception is used is because the underlying classes used to issue a SOAP request can change. For example, the connection can be configured to use HTTP or JMS as the underlying protocol. An HTTP SOAP connection issues a java.net.ConnectionRefused exception if the HTTP service is unavailable, where a JMS SOAP connection issues a javax.jms.JMSException if a JMS-related error occurs.
DAO generation requires the following information:
endPoint
This is the location of the Info*Engine SOAP service. This value is optional and defaults to http://<host>/<Windchill>/servlet/RPC. Depending on your configuration, the default value may not be suitable, in which case you must explicitly specify this information. If credentials are required to access the service then the URL form of “http://<user>:<password>@host/...” should be used.
soapClass
This is the base class (type identifier) from which the DAO is generated. In our example this value is “org.myOrg.Math.”
fileSystem
This value points to a local directory where the root of your Java source tree is located.
package
This is the name of the Java package to which the generated source belongs.
class
This is the name of the class being generated.
There are two ways to generate a DAO: manually invoke a Java command to run the DAO generation tool, or use an Ant extension from an Ant build script.
For the purposes of this example assume the following is true:
You are developing the standalone Java client on the same host where the SOAP service is running.
The SOAP service requires the credentials username (wcadmin) and password (wcadmin).
The root of your Java source tree is /home/user/src.
To generate a DAO for the “org.myOrg.Math” class using a Java command line, invoke the following command (all on one line):
java com.infoengine.connector.dao.DAOGenerator endPoint=http://wcadmin:wcadmin@localhost/
Windchill/servlet/RPC soapClass=org.myOrg.Math fileSystem=/home/user/src package=org.
myOrg.Math class=MathDAO
An Ant build script that generates the DAO should look similar to the following code:
<?xml version="1.0"?>
<project name="generateDAO" default="all" basedir=".">


  <property name="wt.home" value="/opt/ptc/Windchill"/>


  <path id="cp">
    <pathelement location="$(wt.home)/codebase/WEB-INF/
lib/ieWeb.jar"/>

    <pathelement location="$(wt.home)/codebase/WEB-INF/
lib/ie3rdpartylibs.jar"/>
    <pathelement location="$(wt.home)/lib/servlet.jar"/>
  </path>


  <target name="declare">
    <taskdef name="generator" classname="com.infoengine.
connector.dao.AntDAOGenerator">
      <classpath refid="cp"/>
    </taskdef>
  </target>
  <target name="all" depends="declare">
    <generator
      endPoint="http://wcadmin:wcadmin@localhost/Windchill/
servlet/RPC"
      soapClass="org.myOrg.Math"
      fileSystem="/home/user/src"

package="org.myOrg.Math"

class="MathDAO"/>
  </target>


</project>
Using either method of DAO generation results in a Java source file being generated with a fully qualified class name of “org.myOrg.Math.MathDAO” (/home/user/src/org/myOrg/Math/MathDAO.java).
Creating a Connection Handle
Connection handles (instances of javax.resource.cci.Connection) are used to create interactions with Info*Engine. Connection handles do not represent physical connections to Info*Engine. As a result, invoking Connection.close() does not necessarily close the physical connection. Instead, it simply frees the connection handle and allows the physical connection to be returned to a connection pool for later reuse.
Connection handles should always be closed when you are finished using them. Ideally, closing of a connection handle should be performed in a FINALLY block to ensure that, regardless of error conditions, the handle is freed properly and the physical connection can be reused. If this is not done, physical connections can remain marked as busy and never be properly cleaned up. The underlying connection manager is responsible for connection pooling and closing bad or expired physical connections. These are details your source code does not need to deal with.
Connection handles can be retrieved using a connection factory (instance of javax.resource.cci.ConnectionFactory). There are potentially two ways of getting an instance of a connection factory:
The connection factory can be manually configured and created in Java source code.
The connection factory can be retrieved from an LDAP directory.
Looking up a connection factory from an LDAP directory is the preferable method for the following reasons:
Manually configuring and creating the connection factory in source may require code changes if the SOAP service were to move.
Storing the connection factory in an LDAP directory allows many clients to share the connection factory’s configuration.
In this situation updating the location of the SOAP service would only need to be reconfigured in a single place, as opposed to wherever a SOAP client resides. As long as the location of the LDAP directory does not change, clients do not need to be updated.
Managing Connection Factories in the LDAP Directory
Info*Engine supplies the com.infoengine.connector.AdminTool Java class for managing connection factories in the LDAP directory. This tool allows you to bind a new connection factory or unbind an existing connection factory.
The variables and actions used by the tool are supplied as parameters on the Java command. The properties file containing the connections configuration properties must also be supplied.
Variables
principal
The username.
password
The password for the user.
* 
Any parameters being specified from the variables section must precede a single parameter from the actions section.
Actions
The following actions are available from the tool:
-bindConnectionFactory provider object ConnectionImplementation PropertiesFile
Where:
provider is the LDAP system providing the connection.
object is the connection factory being bound.
ConnectionImplementation is the connection implementation specified in the properties file.
PropertiesFile is the name of the Java properties file that contains the connection configuration properties. This properties file is discussed below.
-unbindConnectionFactory provider object
Where:
provider is the LDAP system providing the connection.
object is the connection factory being unbound.
Configuration Properties
The following configuration properties can be specified in the Java properties file:
HTTP Connection Implementation
This property applies to an HTTP connection implementation:
ConnectionURL
Specifies the endpoint of the Info*Engine SOAP service. For example:
http://<host>/<Windchill>/servlet/RPC
JMS Connection Implementation
These properties apply to a JMS connection implementation:
in.queue
Specifies the queue to which the SOAP requests are submitted. This property is required.
out.queue
Specifies the queue on which to wait for the SOAP responses. This property is required.
out.queue.wait
Specifies how long, in milliseconds, to wait for the SOAP response. The default value for this property is -1, which means to wait indefinitely for a response.
provider.url
Specifies the LDAP URL for the location of the subtree containing administered objects. For example:
ldap://localhost/cn=MQSeries,o=MyCompany
This property is required.
provider.principal
If required by your LDAP access controls, specifies the participant needed to bind to provider.url. For example: cn=Manager.
provider.credentials
The password for provider.principal, if required by your LDAP access controls.
queueConnectionFactory
Specifies the relative distinguished name (dn) of the queue connection factory administered object. This property is required.
queueConnectionFactory.user
If required, specifies the username needed to connect to queueConnectionFactory.
queueConnectionFactory.password
If required by your LDAP access controls, specifies the password associated with queueConnectionFactory.user.
Non-Connection Implementation Specific
The following properties are not connection implementation specific:
signRequests
Enables or disables digital signing of SOAP requests. Possible values are TRUE and FALSE, with FALSE being the default value.
keyStoreType
Specifies the type of keystore. The default value for this property is JKS.
keyStorePackageProvider
Specifies the keystore package provider. This property is optional.
keyStoreFilename
Specifies the path to the keystore. The default value for this property is .keystore in the user’s home directory.
keyStorePassword
Specifies the password for the keystore. This property is required.
certificateAlias
Specifies the alias of certificate to use. The default value for this property is iesoap.
privateKeyAlias
Specifies the alias of the private key. The default value for this property is value of the certificateAlias property.
privateKeyPassword
Specifies the private key password. The default value for this property is value of the keyStorePassword property.
Java Properties File
When creating a connection factory you must supply a Java properties file that contains the connection’s configuration properties.
An example of an HTTP connection factory configuration is:
#ConnectionImplementation=com.infoengine.connector.HTTPConnection
ConnectionURL=http://host/Windchill/servlet/RPC
An example of a JMS connection factory configuration is:
#ConnectionImplementation=com.infoengine.connector.JMSConnection
in.queue=cn=SOAP.in
out.queue=cn=SOAP.out
out.queue.wait=60000
queueConnectionFactory=cn=SOAP.qcf
provider.url=ldap://localhost/cn=MQSeries,o=MyCompany
provider.principal=cn=Manager
provider.credentials=admin
Assuming the properties listed previously for an HTTP connection factory were stored in a properties file named http.properties, the following command could be used to bind a new connection factory to an object at the distinguished name cn=cxFactory.HTTP,o=MyCompany:
java com.infoengine.connector.AdminTool -principal=cn=Manager
-password=admin -bindConnectionFactory "ldap://localhost/o=MyCompany"
cxFactory.HTTP com.infoengine.connector.HTTPConnection
./http.properties
The following command could then be used to unbind the connection factory just created:
java com.infoengine.connector.AdminTool -principal=cn=Manager -password=admin
-unbindConnectionFactory "ldap://localhost/o=MyCompany" cxFactory.HTTP
Putting It All Together
The simple Java SOAP client
/home/user/src/org/myOrg/client/Test.java
illustrates how to interact with Info*Engine using SOAP from a standalone application:
package org.myOrg.client;

import org.myOrg.Math.MathDAO;

import com.infoengine.connector.IeConnectionSpec;

import java.util.Hashtable;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.cci.ConnectionFactory;
import javax.naming.InitialContext;
import javax.naming.NamingException;

// only used in undesirable local configured and created
// ConnectionFactory situation
import com.infoengine.connector.IeManagedConnectionFactory;
import java.beans.PropertyVetoException;
import java.io.FileInputStream;
import java.io.IOException;

public class Test {

private static ConnectionFactory getLDAPFactory ()
throws NamingException {

Hashtable env = new Hashtable ( 5 );
env.put ( "java.naming.factory.initial",
"com.sun.jndi.ldap.LdapCtxFactory" );
env.put ( "java.naming.provider.url",
"ldap://ldap.mycompany.com/o=MyCompany" );
env.put ( "java.naming.security.authentication", "simple" );
env.put ( "java.naming.security.principal", "cn=Manager" );
env.put ( "java.naming.security.credentials", "admin" );
InitialContext ctx = new InitialContext ( env );
return (ConnectionFactory)ctx.lookup (
"cn=cxFactory.HTTP " );
}

private static ConnectionFactory getManualFactory ()
throws PropertyVetoException,IOException,ResourceException {

IeManagedConnectionFactory mcf =
new IeManagedConnectionFactory ();
// following optional since HTTPConnection is the default
mcf.setConnectionImplementation (
"com.infoengine.connector.HTTPConnection" );
// configure from properties file
mcf.loadConnectionProperties (
new FileInputStream ( "./http.properties" ) );
// or via method call
// mcf.setConnectionProperties (
// "ConnectionURL=http://localhost/Windchill/servlet/RPC" );
return (ConnectionFactory)mcf.createConnectionFactory ();
}

private static String formatArray ( double [] arr ) {
StringBuffer sb = new StringBuffer ();
for ( int i = 0; i < arr.length; i++ )
sb.append ( arr[i] )
.append ( ( (i+1) < arr.length ) ? " + " : "" );
return sb.toString ();

}
public static void main ( String [] args ) throws Exception {
ConnectionFactory cxf = getLDAPFactory ();
//ConnectionFactory cxf = getManualFactory ();

Connection cx = null;
try {
// get connection with credentials
IeConnectionSpec cxSpec = new IeConnectionSpec ();
cxSpec.setUserName ( "wcadmin" );
cxSpec.setPassword ( "wcadmin" );
cx = cxf.getConnection ( cxSpec );

// or anonymous
//cx = cxf.getConnection ();

MathDAO dao = new MathDAO ( cx, cxf.getRecordFactory () );

int fourAndFive = dao.sum ( 4, 5 );
System.out.println (
"4 + 5 = " + fourAndFive );

double [] nums = new double [] { 4, 5, 6, 7 };
double avg = dao.average ( nums );
System.out.println (
"average of " + formatArray ( nums ) + " = " + avg );

} finally {
if ( cx != null ) cx.close ();
System.exit ( 0 );
}
}
}