Previous     Contents     Index     Next     
iPlanet Application Server 6.5 SP1, Enterprise Edition Programmer's Guide (C++)
Updated: November 25, 2002

Implementation Tips


This appendix includes the following tips:



System Configuration Tips

Install the Web server, the iPlanet Application Server, and the database on different machines so that they will not have to compete for resources. The two servers use Web Connector to communicate.

When using the Web Connector, remember that the CGI variables or NSAPI/ISAPI variables are set on the machine where Web Connector is running, not on the iPlanet Application Server machine. When changing the configuration of Web Connector, make sure to update the registry on the correct machine.


Using Native Methods

When using native methods, be sure to set the SYSTEM_JAVA/GX_CLASSPATH_CORE registry variable. The GX_CLASSPATH_CORE registry variable indicates which classes contain native methods. You don't have to list all the classes, only the prefix characters of groups of classes. For example, if your application includes several classes starting with the package of com.acme.mainframeLink, all of which contain native methods, add the text com.acme.mainframeLink to the settings. Typically, the setting of this variable includes several such strings separated by semicolons.

For security reasons, Java requires that native methods be loaded with a native class loader. At runtime, the Java engine uses the GX_CLASSPATH_CORE registry variable to find out which classes require the native loader. Therefore, if you do not set this registry variable properly, Java will not load the native methods from shared objects correctly.



Memory Management Tips



Java provides garbage collection services to release system resources when objects are no longer needed. Therefore, you do not need to explicitly manage memory in Java. However, it is still not advisable to instantiate thousands of objects so that the garbage collector is kept extremely busy.

In addition, you can call the GX.Release( ) method to explicitly release any internal resources held by objects. For example:

import com.kivasoft.util.*;

IResultSet rs;

// ... get a result set

// Use the result set

// ...

GX.Release(rs);

// ... More code which does not use the result set

When resources are no longer needed, you must explicitly free them by calling the Release( ) method. However, even if you are extremely careful about reference counting, it is likely that some memory will not be released properly. Over time, such memory leaks will consume the available resources on your machine. Therefore, when using C++, it is advisable to use a tool capable of memory use analysis, such as Purify 4.1 by Pure Atria.



Database and Query Tips



To improve database performance:

  • Design the database schema carefully.

  • Tune the database cache sizes and the number of connections.
Query files are easier to maintain than queries written in code using method calls. You can use the Query Designer to set up queries visually, and let the Query Designer automatically generate the query file. You can also write query files using a text editor.

Avoid queries with more than two or three joins, as this will degrade performance. To improve performance, consider denormalizing the database. Denormalization results in duplicate data in the database, but simplifies queries and improves performance in the database.

Use the caching capabilities of iPlanet Application Server to improve performance. Plan on caching early in the design process, because its use affects how you design the client side of the application. All the criteria needed for caching must be present in the input parameters of the request. For example, when the clients are Web browsers, this means the caching criteria must be present as fields on an HTML form, or as arguments in the URL that calls the AppLogic.

When you are finished using a result set, release it by calling GX.Release( ). This method releases the database connection so that it is available for use by other application code. Do not release the result set or close the database connection until you are finished using the result set. Just because the query has run and returned a result set interface, that doesn't mean all the data is there. Typically the result set is buffered, and live database cursors may still be open. Therefore, when you reach the last row in the buffer, the result set object still needs the connection to get the next batch of rows into the buffer.



HTML Tips



To improve the performance of any application with Web browser clients, always use the NSAPI/ISAPI plugin.

Avoid building HTML strings in code, because this is difficult to maintain and update. Keep the HTML in files and templates. HTML designers can then improve and enhance your application's Web browser presentation without having to edit business logic code.



Session Tips



Avoid storing too much data in a session. Every time you save or retrieve the session-related data, the whole IGXValList object is involved. This can impact the performance of the application.



Tips for Calling an Applogic From Another Applogic



By using the nNewRequest( ) method, an AppLogic can call another AppLogic. Usually, this involves a distributed process-to-process communication. This is slower than an in-process local procedure call, but has the added benefits of allowing AppLogic location transparency, load balancing, more support for partitioning, and result caching. For truly fine-grained, often-called operations, however, the remote communication costs are not worth the benefits.



Streaming Tips



The saveSession( )SaveSession( ) method in the AppLogic classGXAppLogic class performs some processing of HTTP headers, which must be sent before the HTTP body. Therefore, if your application uses sessions, and also streams HTML results to a Web browser, be sure to call sSaveSession( ) before calling any streaming methods, including eEvalTemplate( ) or eEvalOutput( ).

The method called sSaveSession( ) exists in both the IGXSession2 interface and the GXAppLogic class. The method in the GXAppLogic class is a wrapper that calls the method in the interface, and performs some tasks that ensure that the session is accessible to future AppLogics. The sSaveSession( ) method in the interface saves session data only. Therefore, be sure to call sSaveSession( ) in the GXAppLogic class at least once after a session is created.


Streaming Results from eEvalTemplate( ) or eEvalOutput( ) Using IGXTemplateData

If you are using an IGXTemplateData object rather than a database query as the source of data for a call to eEvalTemplate( ) or eEvalOutput( ), you can increase the perceived performance of the call by using the following technique. Instead of populating the IGXTemplateData object by calling rRowAppend( ) repeatedly, implement the IGXTemplateData interface yourself and call eEvalTemplate( ) or eEvalOutput( ) much earlier in the AppLogic code. In this way, the Template Engine can call the IGXTemplateData object as it needs data and return results as they are available, keeping the user waiting much less time for a response.

The Template Engine calls the moveNext( )MoveNext( ) method in the IGXTemplateData interface each time it needs a new row of data; for example, when it has completed one pass in a tile tag and is ready to start the next iteration of that tile. If you have implemented your own mMoveNext( ) method, you can use that code to retrieve data as needed. This takes the place of calling rRowAppend( ) repeatedly to populate the IGXTemplateData object all at once. After mMoveNext( ) is called, gGet( ) is called to retrieve the values in that row.

For example, the following code shows how the AppLogic code looks when you use rRowAppend( ):

// Populate the in-memory template data. The number

// of calls to RowAppend() is unlimited. Meanwhile,

// the user is waiting for an unknown length of time

// until the full template data set is populated.

//

GXTemplateDataBasic *td;

td = new GXTemplateDataBasic("offices");

td->RowAppend("office=New York;revenue=150");

td->RowAppend("office=Hong Kong;revenue=130");

// ... add more records here.

// Pass the finished data set to EvalTemplate().

HRESULT hr;

hr = EvalTemplate("salesReportByOffice.html",

   (IGXTemplateData *) td, NULL, NULL, NULL);

td->Release();

return hr;

// Populate the in-memory template data. The number

// of calls to rowAppend() is unlimited. Meanwhile,

// the user is waiting for an unknown length of time

// until the full template data set is populated.

//

TemplateDataBasic td;

td = new TemplateDataBasic("offices");

td.rowAppend("office=New York;revenue=150");

td.rowAppend("office=Hong Kong;revenue=130");

// ... add more records here.

// Pass the finished data set to evalTemplate().

return evalTemplate("salesReportByOffice.html",

   (ITemplateData) td, null, null, null);

Now suppose you create your own implementation of the IGXTemplateData interface or subclass from the GXTemplateDataBasic class. The following code is in the header file:

class MyTemplateDataBasic : public GXTemplateDataBasic

{

public:

   MyTemplateDataBasic(LPSTR group) :

      GXTemplateDataBasic(group)

   {

      // Prepare the retrieval of the offices records here.

      // We don't have to get all the data yet, just

      // the first record data.

   }

   STDMETHOD(IsEmpty) (

      LPSTR group,

      BOOL *empty

   );

   STDMETHOD(MoveNext) (

      LPSTR group

   );

   STDMETHOD(GetValue) (

      LPSTR szExpr,

      IGXBuffer **ppBuff

   );

};

The following code is in the source file:

STDMETHODIMP

MyTemplateDataBasic::GetValue(LPSTR field,

   IGXBuffer **ppBuff)

{

   if (strcmp(field, "offices.office") == 0)

   {

      IGXBuffer *office;

      // ... retrieve current office field value here.

      *ppBuff = office;

      return NOERROR;

   }

   if (strcmp(field, "offices.revenue") == 0)

   {

      IGXBuffer *revenue;

      // ... retrieve current revenue field value here.

      *ppBuff = revenue;

      return NOERROR;

   }

   return GXTemplateDataBasic::GetValue(field, ppBuff);

}

STDMETHODIMP

MyTemplateDataBasic::IsEmpty(LPSTR group, BOOL *empty)

{

   if (strcmp(group, "offices") == 0)

   {

      boolean isOfficeRecordSetEmpty;

      // ... determine if the data set is empty.

      *empty = isOfficeRecordSetEmpty;

      return NOERROR;

   }

   return GXTemplateDataBasic::IsEmpty(group, empty);

}

STDMETHODIMP

MyTemplateDataBasic::MoveNext(LPSTR group)

{

   if (strcmp(group, "offices") == 0)

   {

      HRESULT noMoreRecords;

      // Move to next record in offices data set here.

      // This is where we can dynamically compute

      // the next record.

      //

      // Return NOERROR (0) if next record is available.

      // Return non-zero if no more records.

      return noMoreRecords;

   }

   return GXTemplateDataBasic::MoveNext(group);

}

public class MyTemplateDataBasic extends TemplateDataBasic

{

   public MyTemplateDataBasic(String group)

   {

      super(group);

      // Prepare the retrieval of the offices records here.

      // We don't have to get all the data yet, just

      // the first record data.

   }

   public String getValueString(String field)

   {

      if (field.equals("offices.office"))

      {

         String office;

         // ... retrieve current office field value here.

         return office.

      }

      if (field.equals("offices.revenue"))

      {

         String revenue;

         // ... retrieve current revenue field value here.

         return revenue.

      }

      return super.getValueString(field);

   }

   public boolean isEmpty(String group) {

      if (group.equals("offices"))

      {

         boolean isOfficeRecordSetEmpty;

         // ... determine if the data set is empty.

         return isOfficeRecordSetEmpty;

      }

      return super.isEmpty(group);

   }

   public int moveNext(String group) {

      if (group.equals("offices"))

      {

         int noMoreRecords;

         // Move to next record in offices data set here.

         // This is where we can dynamically compute

         // the next record.

         //

         // Return 0 if next record is available.

         // Return non-zero if no more records.

         return noMoreRecords;

      }

      return super.moveNext(group);

   }

}

The following code shows how the AppLogic code looks when you let the Template Engine retrieve the data through mMoveNext( ):

// Use our own GXTemplateDataBasic subclass, which is

// smart enough to dynamically retrieve office records

// when called back by the template engine. This allows

// data to be streamed back to the user as it becomes

// available, instead of waiting for the entire

// data set to be created first in memory.

//

MyTemplateDataBasic *td;

td = new MyTemplateDataBasic("offices");

// MyTemplateDataBasic retrieves office records

// as necessary, so we do not prepopulate it here.

// Pass the MyTemplateDataBasic object to EvalTemplate().

HRESULT hr;

hr = EvalTemplate("salesReportByOffice.html",

   (IGXTemplateData *) td, NULL, NULL, NULL);

td->Release();

return hr;

// Use our own TemplateDataBasic subclass, which is

// smart enough to dynamically retrieve office records

// when called back by the template engine. This allows

// data to be streamed back to the user as it becomes

// available, instead of waiting for the entire

// data set to be created first in memory.

//

MyTemplateDataBasic td;

td = new MyTemplateDataBasic("offices");

// MyTemplateDataBasic retrieves office records

// as necessary, so we do not prepopulate it here.

// Pass the MyTemplateDataBasic object to evalTemplate().

return evalTemplate("salesReportByOffice.html",

   (ITemplateData) td, null, null, null);



Miscellaneous Tips



You might want to put frequently-used constants or error messages in a separate class or even in a database, so that they are easy to find and modify. For example:

package com.acme.onineInventory;

public class Constants {

   public static String APPNAME = "Online Inventory Control";

   public static String APPNAME_SHORT = "OIC";

   public static String DATASOURCE = "OIC_DB";

   // ...

}

Previous     Contents     Index     Next     
Copyright © 2002 Sun Microsystems, Inc. All rights reserved.

Last Updated November 25, 2002