Adapters are the interface between the IntelliPortal server and external data sources. You create specific adapters for each data source required by IntelliPortal applications (e.g. channels). These might include databases of customer information on your own legacy systems; advertisement servers; or third-party providers of financial information. Each client request for data that cannot be filled from the local cache results in an instance of the proper adapter being created. IntelliPortal provides a standard interface (IAdapter) that you implement when creating an adapter so that other IntelliPortal objects know how to talk to it. This section explains three ways to work with adapters.
See the Server Administrator Guide for details how configure an adapter so that it can be used by a IntelliPortal channel. |
___________________________________
This section explains the methods you can use to create new adapters. If you are developing adapters for financial transaction applications, see Developing Financial Transaction Adapters.
The only coding requirement for developing a new adapter is that your class implement com.mecasw.eb.adapters.IAdapter. This is the interface that adapters must implement in order to receive and respond to datafeed requests. (datafeed is the standard interface through which requests for information are routed to external adapters). IAdapter is the internal IntelliPortal interface, implemented by a host interface, that is used to communicate with datafeed. It has the following methods.
This method is called by the server to query an adapter and report what features or attributes are supported. It returns true if the feature or attribute is supported. Standard features are defined in this class and additional features can be defined by system administrators.
The most important method you need to implement is process(), which is what the server may call in response to a client request for data. This method needs to be implemented in two flavors: streaming and non-streaming applications. (Note that only non-streaming is currently available.) Use the following signature for streaming applications:
public void process(InputStream instream, OutputStream outstream) throws AdapterException
Use the following signature for non-streaming applications:
public Object process(Object inobject) throws AdapterException
For non-streaming applications, the input Object generally encapsulates the data request, and the return Object contains the requested data. Usually, input is a string, and output is a byte array, DataBundle (see below), or string. Typically the byte array can be a serialized object that is unserialized by the client. The section Dynamic Updating describes how your channel would prepare the data request and process the returned data. A typical implementation of process() does the following.
This method provides a context reference for the adapter for this host session. It is called by the server and should be implemented to get the adapter context and make a local reference for future use. You only need to implement setContext() to save the reference if you will need this reference in the future.
When an adapter is instantiated prior to calling the process() method, setContext() is called and passed a context. Every adapter instance has a context associated with it. This context may be empty. The context is the mechanism used to pass extra parameters from the host system to the adapter. When other methods like process() are being used, you can query the context for additional information saved in the context. The information in the context is set using the Server Administrator. It includes extra parameters, environment variables, etc. It also defines standard member names that are used in queries for supported features with isSupported().
If the adapter requires entries in its context, it must implement com.mecasw.eb.adapters. IAdapterContextInfo. The implementation should define all those items the adapter wishes to have handed to it in its context. When the Server Administrator runs, the adapter is queried. If it implemented the IAdapterContextInfo interface, its getAdapterContextInfo() method is called, the information is presented to the Server Administrator, and appropriate values must be entered. If the AdapterContext is to contain a class or host adapter, those classes will also be queried to see if they require any context information. The API describes how and when to use IAdapterContextInfo.
IntelliPortal includes a class called DataBundle that you can use to efficiently bundle a variable number of data pieces and describe their properties. It basically creates a mini-database structure, which encapsulates the data itself, its MD5 hash, its creation time, its size, and a name for the data.
A typical application would be a client request for news stories for a particular stock symbol. You can use a DataBundle to neatly return all the stories in one object whose properties are known by other IntelliPortal processes. The preferred method of instantiating a DataBundle is DataBundle(int capacity) where capacity indicates the number of records you need. If you instantiate it with no parameters, it constructs a DataBundle with a default size of 10 records.
DataBundles contain the following methods:
If the number of records in the object is already at capacity, the object is dynamically resized. Avoid this inefficient scenario if possible. |
Use the following methods to access the individual data items or their attributes. Each of these accepts an int which is the record number within the DataBundle and throws a DataFeedException if this index value is out-of-bounds.
This section explains how to create adapters that are used for processing financial transactions such as fund transfers, account statements, etc. via OFX (Open Financial Exchange). What distinguishes these adapters from others is that they have to implement the interface IFinancialHost in addition to IAdapterContextInfo (which all other adapters also need to implement).
The interface IFinancialHost has only one method:
public void process(IFinancialContext fc) throws ConnectionException;
A complete implementation of this method does the following:
The interface IAdapterContextInfo also has one method:
public AdapterContextDataInfo getAdapterContextDataInfo();
This method must be implemented so that the Server Administrator tool can remotely configure the adapter with the properties it needs to make a connection to the host server or to set the timeout. For example, if it makes an HTTP connection to the host server, details like timeout and various HTTP headers should be returned as part of AdapterContextDataInfo.
At a minimum, all financial transaction adapters must import the following three packages. Depending on your requirements, you may also need to import others (e.g. java.io for streaming applications).
com.mecasw.financials.ofx.*;
com.mecasw.eb.adapters.*;
public class MyHostAdapter implements IFinancialHost,IAdapterContextInfo
{
public void process(IFinancialContext fc) throws ConnectionException
{
// Do the request processing here
}
public AdapterContextDataInfo getAdapterContextDataInfo()
{
// Create and return AdapterContextDataInfo
}
}
Extracting the Request for Financial Information
If the host server is an OFX server, you can pass the request on as OFX formatted stream by using the following code in the process() method:
String sendToHostServer = fc.getOutgoingOfxString();
If the host server follows a protocol other than OFX, you can break the request into smaller pieces and create a data stream as per the host server protocol as follows:
OfxRequest request = fc. getOfxRequest(); SignOnRequest sorq = request.getSignOnRequest(); String userName = sorq.getUserName(); String userPwd = sorq.getUserPassword(); ... ... String sendToHostServer = new String(userName + ":" + userPwd ...); //Use the host protocol
Before you can send the request over to the host server, you must establish a connection. To do that, you may need to set certain connection properties with the Server Administrator. For that to happen, you need to specify those properties. For example, to make an HTTP connection, you need to do the following:
static String HTTP_SERVER_URL = "ServerURL";
static String HTTP_ HEADER_ACCEPT = "Accept";
static String HTTP_ HEADER_CONNECTION = "Connection";
static String HTTP_ HEADER_PRAGMA = "Pragma";
static String HTTP_ HEADER_USER_AGENT = "User-agent";
static String HTTP_ HEADER_CONTENT_TYPE = "Content-type";
static String TIMEOUT_IN_MS = "Timout";
public AdapterContextDataInfo getAdapterContextDataInfo()
{
AdapterContextDataInfo info = new AdapterContextDataInfo(7);
info.add(HTTP_SERVER_URL, STRING);
info.add(HTTP_HEADER_ACCEPT, STRING);
info.add(HTTP_HEADER_CONNECTION, STRING);
info.add(HTTP_HEADER_PRAGMA, STRING);
info.add(HTTP_HEADER_USER_AGENT, STRING);
info.add(HTTP_HEADER_CONTENT_TYPE, STRING);
info.add(TIMEOUT_IN_MS, STRING);
return(info);
}
public void process(IFinancialContext fc) throws ConnectionException
{
OfxResponse response = null;
SignonResponse sors = null;
Status status = null;
OfxRequest request = fc. getOfxRequest();
SignOnRequest sorq = request.getSignOnRequest();
try
{
response = (OfxResponse) OfxInstanceMgr.getNewInstance
("OfxResponse");
sors = (SignOnResponse) OfxInstanceMgr.getNewInstance
("SignOnResponse");
status = (Status) OfxInstanceMgr.getNewInstance ("Status");
}
catch (OfxException e) {}
status.setStatusCode("0"); // success
status.setStatusSeverity("INFO");
sors.setStatus( status );
sors.setServerResponseDate(new Date()); // success
if (sorq.isGenerateUserKey())
{
// The client requested that the user key be generated.
sors.setUserKey("Dummy User Key");
GregorianCalendar cal = new GregorianCalendar();
cal.add(Calendar.DATE, 7);
sors.setKeyExpirationDate(cal.getTime());
}
sors.setLanguage(sorq.getLanguage());
sors.setProfileUpdateDate(new Date());
sors.setAccountUpdateDate(new Date());
sors.setFinancialInstitution(sorq.getFinancialInstitution());
sors.setSessionCookie("Save this cookie");
response.setSignOnResponse(sors);
fc.setOfxResponse(response);
}
If you make an HTTP connection to the host server, you need to add the code as follows:
public void process(IFinancialContext fc) throws ConnectionException
{
...
...
...
HttpConnection myConn = new HttpConnection();
String val = (String)fc.get(HTTP_SERVER_URL);
URL url = new URL( val );
myConn.setURL(url);
val = (String)fc.get(HTTP_HEADER_ACCEPT);
myConn.setRequestProperty(HTTP_HEADER_ACCEPT, val);
val = (String)fc.get(HTTP_HEADER_CONNECTION);
myConn.setRequestProperty(HTTP_HEADER_CONNECTION, val);
val = (String)fc.get(HTTP_HEADER_PRAGMA);
myConn.setRequestProperty(HTTP_HEADER_PRAGMA, val);
val = (String)fc.get(HTTP_HEADER_USER_AGENT);
myConn.setRequestProperty(HTTP_HEADER_USER_AGENT, val);
val = (String)fc.get(HTTP_HEADER_CONTENT_TYPE);
myConn.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, val);
val = (String)fc.get(TIMEOUT_IN_MS);
myConn.setTimeout( new Integer(val) );
myConn.setOutgoingData(sendToHostServer);
String retStr = myConn.getData();
fc.setIncomingOfxString(retStr);
}
IntelliPortal includes the following pre-built adapters, among others, in the package com.mecasw.eb.adapters. To use one of these adapters, just use the Server Administrator to associate the adapter with a channel. The appropriate metadata records must be created or modified using the Server Administrator. See the section Using Triggers and History for a description of a channel's direct file access capabilities.
This adapter accepts either a String or Integer as input and wills return a byte array specified by the input. It can be used to test that a request comes from a client, reaches the adapter, and that the result is returned. It does the following:
Use the FileReaderAdapter class to retrieve information from a specific directory in an external data source. The information can be saved in cache and can be retrieved as appropriate. It does the following:
The OfxAdapter class is used for processing financial transactions such as fund transfers, account statements, etc. What distinguishes OfxAdapter from other adapters is that it implements the interface IFinancialHost.
Before you can use OfxAdapter, you need to point it at an OFX server in order to make it functional. To point it at a server, use the Server Administrator to create and configure a datafeed record that points to the server URL. (See the Server Administrator Guide if you don't know how to create a datafeed record.)
![]() |
To configure an OFX adapter |
Use the URLAdapter class to retrieve HTML content from a web server. You can use it for any information that you can get through HTTP. It reads a URL and returns text, graphics, or other data to the client application from an external source. You can use URLAdapter to open and retrieve data from either a file or from the output of a HTTP GET command (provided that the complete CGI string is encoded in the URL).
You can get and store any data by sending the appropriate command; no additional code is required if the metadata is set up correctly in the Server Administrator. It does the following.
Use this adapter to retrieve a web page for display or to get any kind of non-HTML data (GIF files, audio files, serialized objects, etc.). Note that the URLAdapter does not currently support SSL.
The WebCrawlerAdapter provides an interface that lets a channel retrieve the contents of a specified web site. The HTML pages are retrieved along with any referenced images and active server pages. The retrieved objects are localized for the destination and packaged into a DataBundle object that is returned to the calling channel. In this context, localization means that the references and image locations in an HTML page are modified from absolute references to relative references (relative to the main page being downloaded).
The WebCrawlerAdapter is recursive. It starts from the specified root page and drills down, retrieving all references from one level to the next. This process continues until it retrieves the entire depth or until it reaches the maximum depth specified by the AdapterContext. The adapter is passed the URL of the web site to retrieve in the process() method of the IAdapter interface and returns the DataBundle object from this method.
The examples that follow concentrate on the actual adapter and client code. These examples assume that the adapter and channels are properly set up using the Server Administrator.
The following code is a good example of how to write an adapter. It gets an object and creates a byte array of the appropriate size. It can actually be used to debug a test connection.
package com.mecasw.eb.adapters;
import java.io.*;
import java.net.*;
/**
* Returns a buffer of a determined size for end to end testing.
* The buffer will have nothing of significance in it.
* <br>
* @version $Revision: 1 $
* @author mecasw
* @see
*/
public class ByteArrayTestAdapter implements IAdapter
{
/**
* Returns a byte [] of the specified size.
* If null is passed, a byte [] of 8192 is used.
* The argument may be a String representation
* of an int or an Integer or null. If the String doesn't represent an
* integer, 8192 is used.
* <br>
* @param obj String or Integer representing the size of the array to
* send or null for default size.
* @return byte[] buf containing the data
* @exception throws AdapterException
*/
public Object process(Object obj) throws AdapterException
{
int size;
if(obj == null)
size = 8192;
else if(obj instanceof String)
{
try
{
size = Integer.parseInt((String)obj);
}
catch(NumberFormatException e)
{
size = 8192;
}
}
else if(obj instanceof Integer)
size = ((Integer)obj).intValue();
else
throw new AdapterException(AdapterException.UNSUPPORTED_INPUT_TYPE);
return (new byte[size]);
}
/*
* Unimplemented....
*/
public void process(InputStream in, OutputStream out)
throws AdapterException
{
throw new AdapterException(AdapterException.UNSUPPORTED_INPUT_TYPE);
}
/**
* Tell if a feature or context is supported.
* @return true fi supported, else false.
* @param feature The name of the feature
* @see IAdapter
*/
public boolean isSupported(String feature)
{
return(feature == OBJECT_IO);
}
/**
* Set the context.
* This class does not use it.
*/
public void setContext(AdapterContext context)
{
// do nothing....
}
}
The client code could send a String or an Integer and the adapter would return
an array of that size. Here is the channel code:
public class Loopback extends MecaBaseChannel
{
public void start()
{
if ( getContext() != null )
{
// We are a channel. Let us initialize the gui components
// and let the super class handle everything else.
super.start();
IRequestBufferService rBufService =
(IRequestBufferService)getContext().getService(REQUEST_BUFFER_SERVICE);
try
{
IRequestBuffer rBuf = rBufService.getBuffer();
// get a 10,000 bye buffer
// A string could have been added too like this:
// rBuf.add("10000");
rBuf.add(new Integer(10000));
if(rBufService.commitBuffer(rBuf, getContext()) == false)
System.out.println("commitBuffer() failed! ");
getContext().update();
}
catch (IOException e) {
System.out.println("Exception " + e.getMessage());
}
}
}
}
The following is an example of an adapter that uses a DataBundle to return three separate pieces of data in one call. The data includes a byte Array, a serialized Integer, and serialized Hashtable.
package com.mecasw.eb.adapters;
import com.mecasw.util.MECAUtil;
import com.mecasw.eb.datafeed.DataBundle;
/**
* Shows how to use a DataBundle.
*
* <br>
* @version $Revision: 1 $
* @author mecasw
* @see
*/
public class DataBundleAdapter implements IAdapter
{
/**
* Returns a DataBundle containing an Integer a byte array and a
Hashtable. The name of each piece
* of data will be what it is.
* <br>
* @param obj ignored
* @return DataBundle
* @exception throws AdapterException
*/
public Object process(Object obj) throws AdapterException
{
Hashtable ht = new Hashtable();
DataBundle bundle = new DataBundle(3);
Integer anInt = new Integer(126);
byte buf[] = new byte[1024];
.
.
.
// assume stuff added to byte array and Hashtable....
bundle.addData(buf, null, buf.length, System.currentTimeMillis(),
"byte array");
// MECAUtils hs ObjectToByteArray which creates a byte[] from an
// Object. There is the sistem methow byteArrayToObject() which
takes
// a byte[] and turns it back to a Object.
byte serializedObj[] = MECAUtil.objectToByteArray(anInt);
bundle.addData(serializedObj, null, buf.length,
System.currentTimeMillis(),
"Integer");
serializedObj[] = MECAUtil.objectToByteArray(ht);
bundle.addData(serializedObj, null, buf.length,
System.currentTimeMillis(),
"Hashtable");
return(bundle);
}
/*
* Unimplemented....
*/
public void process(InputStream in, OutputStream out)
throws AdapterException
{
throw new AdapterException(AdapterException.UNSUPPORTED_INPUT_TYPE);
}
/**
* Tell if a feature or context is supported.
* @return true fi supported, else false.
* @param feature The name of the feature
* @see IAdapter
*/
public boolean isSupported(String feature)
{
return(feature == OBJECT_IO);
}
/**
* Set the context.
* This class does not use it.
*/
public void setContext(AdapterContext context)
{
// do nothing....
}
}
Any serializable Object the channel code wishes to send using the IRequestBuffer facility is passed to the appropriate adapter. Some example are Vectors, Hashtables, Strings, Integers, custom-written Objects (e.g. a CreditCardApplication Object), etc. In this example, the adapter casts the object to the expected type.
public Object process(Object obj) throws AdapterException
{
if(obj == null)
return(null);
else if(obj instanceof Hashtable)
{
// Do your processing here....
}
else if(obj instanceof CustomObject)
{
// Do your processing here....
}
else // I don't know what to do with it.
throw new AdapterException(AdapterException.UNSUPPORTED_INPUT_TYPE);
Currently, adapters return Strings, byte() or DataBundles only. If you need to send an Object back, it should be serialized before you send it.
Before the adapter's process() method is called, the datafeed prepares an AdapterContext Object and gives the adapter access to it by calling the adapter's setContext() method. The elements added to the AdapterContext are determined by querying the adapter as discussed below. The values of the elements are determined by the metadata for that particular datafeed/adapter pair.
Some examples of things that may be included in the context are the client user's ID, a Property Object, String, or any other Object that can be instantiated by the IntelliPortal server. Additionally, the AdapterContext can be used by adapters to send information back to the IntelliPortal server. Currently, the only information set by the adapter that the IntelliPortal server recognizes is AdapterContextConstants.TIMESTAMP. This is used to record the creation time of the data returned by the adapter. Note that if this is not added and this data is to be cached, the current time is used.
The following example demonstrates the rule for implementing IAdapterContextInfo. If you are handed a context object that implements com.mecasw.eb.adapters.IAadapterContext, and you require some information to be given to you, you must implement IAdapterContextInfo.
public class ContextExample implements IAdapter, IAdapterContextInfo
{
private AdapterContext m_context;
Integer userId;
String url;
private final String HOST_ADAPTER_USERID = "UserID";
private final String HOST_ADAPTER_KEY_NAME = "HostAdapter";
private final String HOST_CONNECT_URL = "ConnectionURL";
public Object process(Object in) throws AdapterException
{
//Currently, we're accepting only an array of Hashtables
if (!(in instanceof Hashtable[]))
{
throw new AdapterException(AdapterException.UNKNOWN_FORMAT);
}
else
{
// USERID is the unique ID used by IntelliPortal to identify
// a user.
userId = (Integer) m_context.get(HOST_ADAPTER_USERID);
url = (String) m_context.get(HOST_CONNECT_URL);
// Now we get a Host specific class and pass it the hashtable
Host Connector he = (HostConnector) m_context.get(HOST_ADAPTER_USERID);
hc.sendTableToHost(hc, url);
}
// put the time this data was created in the Context...
// The server will use it for caching.
}
/*
* This is where the AdapterContext is called and if you need
* to use its contents or set its contents, get a reference to
* it here.
*/
public void setContext(AdapterContext context)
{
m_context = context;
}
/*** This is implemented from IAdapterContextInfo. It will
* be called by the Administrator to see what information
* Should be placed in the AdapterContext prior to calling
* process(). This adapter needs a HOSTADAPTER, a USERID
* and a STRING which is a URL passed to it in its adapterContext.
*/
public AdapterContextDataInfo getAdapterContextDataInfo()
{
// AdapterContextInfo is the Object that holds the information
AdapterContextDataInfo info = new AdapterContextDataInfo(1);
/*
* HOSTADAPTER, USERID and STRING are defined in
IAdapterContextInfo
* First, we want a host adapter...
*/
info.add(HOST_ADAPTER_KEY_NAME, HOSTADAPTER);
// next the user ID
info.add(HOST_ADAPTER_USERID, USERID);
// Finally, the String URL
info.add(HOST_CONNECT_URL, STRING);
return(info);
}
Adapters throw AdapterExceptions. AdapterException extends MECAException. The following example shows a mechanism that allows an integer association with an exception. This makes programmatic analysis of exceptions a trivial task and the javadocs for AdapterException show several generic, pre-defined exceptions that you can use. An example is when your adapter gets an Object as input that it was not prepared to use. As shown in the example, you would throw an UNSUPPORTED_INPUT_TYPE exception.
if(!(obj instanceof Vector))
{
throw new AdapterException(AdapterException.UNSUPPORTED_INPUT_TYPE);
}
In many cases when writing adapters, it may be necessary to add specific exceptions. To do this you can extend AdapterException and throw those exceptions. The common way to do this is to have your specific error codes begin at some arbitrary high value like 2000. Then override getMessage(), have it analyze the error code that was set when the instance was instantiated, and either pass it to the super's getMessage() or manage it itself. The following example of DemoAdapterException does just this.
package com.mecasw.idd.adapters;
import com.mecasw.eb.adapters.AdapterException;
/**
* Demo Exception class for classes implementing IDD Adapter Exceptions.
*<br>
* @version $
* @author mecasw
* @see com.mecasw.eb.adapters.IAdapter
*/
public final class DemoAdapterException extends AdapterException
{
/**
* Construct an exception with a message
*<br>
* @param msg The message for the constructed exception
*/
public DemoAdapterException( int cause )
{
this( cause, null );
}
public DemoAdapterException( int cause, Exception e )
{
super( cause , e );
}
public String getMessage()
{
if(errorCode >= 2000)
return iddMsgStrings[ errorCode - 2000];
return(super.getMessage());
}
public static final String [] iddMsgStrings =
{
"Demo Adapter could not connect to the Lotus Notes Server.",
"Demo Adapter got a bad response from the Lotus Notes Adapter.",
"Demo Adapter Did Something really bad..."
};
public static final int DEMO_CANT_CONNECT = 2000;
public static final int DEMO_BAD_RESPONSE = 2001;
public static final int DEMO_TIMED_OUT = 2002;
public static final int DEMO_DID_BAD_THING = 2003;
}
The MECA Storage Facility (MSF) is primarily designed for client-side storage requirements relating to channels. The JDBC is recommended for server-side storage requirements. JDBC (Java DataBase Connectivity) is a programming interface that lets Java applications access a database via SQL statements. The class com.mecasw.eb.util.server.IUserTransientStorage is the preferred method for server-side storage. Within certain guidelines, JDBC provides the level of abstraction needed for database independence as well as optimal performance.
Although com.mecasw.eb.util.server.IUserTransientStorage is sufficient for most storage requirements, IntelliPortal provides interfaces to other user storage and utility components that can help adapter developers to access other databases. Other utilities, strategies, and topics in this section include:
IntelliPortal creates an IntelliPortal user during the enrollment process and assigns this user a unique userid. This user has some permanent information that is managed through the interface com.mecasw.eb.util.server.IUserPermanentStorage.java. The information that is available depends on the site-specific implementation of enrollment. It is returned in a java.util.Hashtable as name/value pairs. Generally, the only reason you need to use this facility is to get the mapping of the IntelliPortal userid to a host-specific identifier. Note that the instance for this class and com.mecasw.eb.util.server.IUserTransientStorage is provided by com.mecasw.eb.util.server.UserStorageFactory. Here is a code snippet that shows how to use this facility:
import com.mecasw.eb.util.server.IUserPermanentStorage;
.
.
.
IUserPermanentStorage user = null;
try
{
user = UserStorageFactory.getPermanentInstance();
if((ht = user. getEBranchUserByEbranchId (id)) == null)
System.out.println("There is no user by id: " + id);
else
{
for(Enumeration e = ht.getKeys(); e.hasMoreElements() ;)
{
key = (String)e.nextElement();
System.out.println("Key: " + key + " Value: " +
ht.get(key));
}
}
}
Adapters live only long enough to perform some desired function. However it may be necessary to hold information for a given user on the server until requested by an instance of an adapter. The facility to do this is com.mecasw.eb.util.server.IUserTransientStorage.java. This is specifically designed to provide a temporary holding place for personal user data. An example might be a credit card application-processing adapter. This adapter might get a credit card application and send it to a host-specific facility for processing. When the response is ready, a program would place the information in IUserTransientStorage. When executed, the adapter checks to see if there anything for it in transient storage and responds appropriately.
This facility can store any serializable Object in the database for the user. It is keyed on userid and a String that can be any arbitrary identifier you choose. This way several different pieces of information can be stored independently for a given user. The facility is generic enough for most adapter developer requirements.
If IUserTransientStorage is not adequate, you can use custom tables created with SQL scripts. Since server efficiency is a primary concern and establishing a database connection has high overhead, you should access the database through connection pools (see below). Additionally, you can simplify access by using com.mecasw.util.DbUtils as also discussed below.
You can also access other databases from the IntelliPortal database. If the connection overhead is high, you can create a pool using com.mecasw.util.ObjectPool. This is a base class for building pools of objects and is the super class for IntelliPortal database connection pools. You can use the utilities in com.mecasw.util.DbUtils to access any database as long as you use JDBC.
The class com.mecasw.eb.util.server.SharedConnectionPool.java is designed to limit the overhead needed to make database connections. This mechanism reuses connections and reduces the overhead needed for instantiation. There is also a local shared connection pool called com.mecasw.eb.util.server.LocalConnectionPool.java. This is used for sites that have differentiated between shared database connections (for data that is shared between redundant hosts) and local connections (that restrict data to the local host). You should be using shared connections for most adapters. Here is an example:
import java.sql.Connection;
import com.mecasw.eb.util.server.SharedConnectionPool;
.
.
.
Connection con;
if((con = SharedConnectionPool.checkOutCon()) == null)
{
throw
new AdapterException(AdapterException.SQL_EXCEPTION, e);
}
.
.
.
The use of flat files is discouraged. Although you can use flat files for server storage, there are certain constraints that make their use problematical. For example, the server is potentially replicated to other hosts for performance, and update requests may go to any host. If you use flat files in this environment, the files must be copied to all replicated hosts and when updated, removed from all replicated hosts. You also need to consider threading when reading and writing these files.
Other JDBC utilities are available for Update, Insert, Delete, Create, Select, getting unique integer IDs, and other miscellaneous tasks. Many of the utilities also provide a logging function for recording generated and executed statements. Generally database access is relatively simple and does not require joins, dynamic SQL, and other advanced features. There is also a collection of useful methods in com.mecasw.util.DbUtils. With these facilities, you usually pass in a database connection, a java.util.Hashtable with the appropriate information, and the return will be the result of the database operation. The appropriate JDBC statements are generated and executed. This simplifies most database access. Here is a code sample:
Connection con;
con = getConnection();
try
{
ht = new Hashtable();
ht.put("Name", John Smith");
rows = DbUtils.simpleDelete(con, "AGivenTable", ht);
}
catch (SQLException e)
{
throw
new AdapterException(AdapterException.SQL_EXCEPTION, e);
}
finally
{
SharedConnectionPool.checkInCon(con);
}