My Quotes


When U were born , you cried and the world rejoiced
Live U'r life in such a way that when you go
THE WORLD SHOULD CRY






Wednesday, December 23, 2009

JBoss Port Binding Error


1.   Cause of Error

JBoss comes with many socket based services that open listening ports

This might be due to the reason that JBoss is having conflict with some of the ports with other applications and thus causing JBoss not to start properly.



2.   Log file to look into


<<JBOSS_HOME>>\server\default\log\server.log



3.   Log message to look into


<<Latest Time Stamp>> WARN  [ServiceController] Problem starting service jboss:service=Naming
java.rmi.server.ExportException: Port already in use: 1098; nested exception is: 
        java.net.BindException: Address already in use
        at sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:243)
        at sun.rmi.transport.tcp.TCPTransport.exportObject(TCPTransport.java:178)
        at sun.rmi.transport.tcp.TCPEndpoint.exportObject(TCPEndpoint.java:382)
        at sun.rmi.transport.LiveRef.exportObject(LiveRef.java:116)

The above port number highlighted in bold is just a sample. This can be potentially any port number which could cause Jboss not to startup properly.



4.   List of port that could potentially cause the problem


The suggested list of ports of JBoss which could cause the BindException is
the typical ports JBoss uses. A range of 1098-8093 certainly covers the default port usage for jboss

Jboss-4.0.3SP1\server\default\





Additional ports found in the all configuration:




5.   Sample port configuration changes


The following sections illustrate the customer support representative with the frequent port number which might cause the conflict and their suggested file path.
This also enables the support personnel to identify the exact match XMl tags they need to look in from the document which is of ease.

In all these cases stop the JBoss service as given in step 6. Do the changes and then re-start the service.


{JBoss_Install_Dir}\server\default\conf\



File Name                              
jboss-service.xml

Tag Block to look for

  • Port for JNP
<!-- The listening port for the bootstrap JNP service. Set this to -1
        to run the NamingService without the JNP invoker listening port.
-->
            <attribute name="Port">1100</attribute>
This requires a change in the application too. Please escalate to the first level escalation.

  • Port for RMI
<!-- The port of the RMI naming service, 0 == anonymous -->
      <attribute name="RmiPort">1097</attribute>
Change the port 1097 by 11097

{JBoss_Install_Dir}\server\default\deploy\jbossweb-tomcat55.sar\

File Name

server.xml

Tag Block to look for

      <!-- A HTTP/1.1 Connector on port 351 -->
            <Connector port="351" address="${jboss.bind.address}"
      This requires a change in the application too. Please escalate to the first level
     Escalation.

Java VM Tuning

1.      Introduction

This Java Virtual Machine document is intended as a reference for Java Performance Tuning information, techniques and pointers.

1.1.    Goals


The goals of this document is to collect the best practices and "How To" for Java Performance in one place.

The initial target for this tuning document is tuning server applications on large, multi-processor servers. Future versions of this document will explore similar recommendations for desktop Java performance.


2.      Categories of Java HotSpot VM Options

Standard options recognized by the Java HotSpot VM are described on the Java Application Launcher reference pages for Windows, Solaris and Linux. This document deals exclusively with non-standard options recognized by the Java HotSpot VM:
·         Options that begin with -X are non-standard (not guaranteed to be supported on all VM implementations), and are subject to change without notice in subsequent releases of the JDK.
·         Options that are specified with -XX are not stable and are not recommended for casual use. These options are subject to change without notice

3.      Tuning Techniques


3.1.    Ergonomics Settings


Before starting to tune the command line arguments for Java be aware that Sun's HotSpot™ Java Virtual Machine has incorporated technology to begin to tune itself. This smart tuning is referred to as Ergonomics. Most computers that have at least 2 CPU's and at least 2 GB of physical memory are considered a server-class machine which means that by default the settings are:
·         The -server compiler
·         The -XX:+UseParallelGC parallel (throughput) garbage collector
·         The -Xms initial heap size is 1/64th of the machine's physical memory
·         The -Xmx maximum heap size is 1/4th of the machine's physical memory (up to 1 GB max).
Please note that 32-bit Windows systems all use the -client compiler by default and 64-bit Windows systems which meet the criteria above will be treated as server-class machines

3.2.    Heap Sizing


Ergonomics significantly improves the "out of the box" experience for many applications, but optimal tuning often requires more attention to the sizing of the Java memory regions.

The maximum heap size of a Java application is limited by three factors: the process data model (32-bit or 64-bit) and the associated operating system limitations, the amount of virtual memory available on the system, and the amount of physical memory available on the system. The size of the Java heap for a particular application can never exceed or even reach the maximum virtual address space of the process data model.

For 32-bit, the maximum is 4GB and for 64-bit it is unlimited.

For a single Java application on a dedicated system, the size of the Java heap should never be set to the amount of physical RAM on the system, as additional RAM is needed for the operating system, other system processes, and even for other JVM operations.

On systems with multiple Java processes, or multiple processes in general, the sum of the Java heaps for those processes should also not exceed the size of the physical RAM in the system

The next most important Java memory tunable is the size of if the young generation (also known as the NewSize). Generally speaking the largest recommended value for the young generation is 3/8 of the maximum heap size.

3.3.    Garbage Collector Policy


The Java™ Platform offers a choice of Garbage Collection algorithms. For each of these algorithms there are various policy tunables. Instead of repeating the details of the Tuning Garbage Collection document here suffice it to say that first two choices are most common for large server applications:
·         The -XX:+UseParallelGC parallel (throughput) garbage collector, or
·         The -XX:+UseConcMarkSweepGC concurrent (low pause time) garbage collector (also known as CMS)
·         The -XX:+UseSerialGC serial garbage collector (for smaller applications and systems

3.4.    Other Tuning Parameters

By appropriately configuring the operating system and then using the command line options -XX:+UseLargePages (on by default for Solaris) and -XX:LargePageSizeInBytes can get the best efficiency out of the memory management system of the server. Note that with larger page sizes we can make better use of virtual memory hardware resources (TLBs), but that may cause larger space sizes for the Permanent Generation and the Code Cache, which in turn can force to reduce the size of Java heap. This is a small concern with 2 MB or 4 MB page sizes but a more interesting concern with 256 MB page sizes.

An example of a Solaris-specific tunable is selecting the libumem alternative heap allocator. To experiment with libumem on Solaris ,use the following LD_PRELOAD environment variable directive:

·         To set libumem for all child processes of a given shell, set and export the environment variable
LD_PRELOAD=/usr/lib/libumem.so

·         To launch a Java application with libumem from sh:
LD_PRELOAD=/usr/lib/libumem.so java java-settings application-args

·         To launch a Java application with libumem from csh:
env LD_PRELOAD=/usr/lib/libumem.so java java-settings application-args

Default values are listed for Java SE 6 for Solaris Sparc with -server. Some options may vary per architecture/OS/JVM version. Platforms with a differing default value are listed in the description.
·         Boolean options are turned on with -XX:+<option> and turned off with -XX:-<option>.
·         Numeric options are set with -XX:<option>=<number>. Numbers can include 'm' or 'M' for megabytes, 'k' or 'K' for kilobytes, and 'g' or 'G' for gigabytes (for example, 32k is the same as 32768).
·         String options are set with -XX:<option>=<string>, are usually used to specify a file, a path, or a list of commands
Flags marked as manageable are dynamically writeable through the JDK management interface (com.sun.management.HotSpotDiagnosticMXBean API) and also through JConsole. In Monitoring and Managing Java SE 6 Platform Applications, Figure 3 shows an example. The manageable flags can also be set through jinfo -flag.

The options below are loosely grouped into three categories.

·         Behavioral options change the basic behavior of the VM.
·         Performance tuning options are knobs which can be used to tune VM performance.
Debugging options generally enable tracing, printing, or output of VM information

3.4.1.        Behavioral Options






 


3.4.2.        Performance Options









3.4.3.        Debugging Options










4.      Monitoring and Profiling

4.1.    Monitoring

The Java™ Platform comes with a great deal of monitoring facilities built-in. Please see the document Monitoring and Management for the Java™ Platform for more information.

The most popular of these "built-in" tools are JConsole and the jvmstat technologies.J2SE 5.0 includes the following APIs for monitoring and management
:

4.1.1.1.  Java Virtual Machine Monitoring and Management API

The java.lang.management API enables monitoring and managing the Java virtual machine and the underlying operating system.  The API enables applications to monitor themselves and enables JMX-compliant tools to monitor and manage a virtual machine locally and remotely.
Example code is provided in the JDK_HOME/demo/management directory.

4.1.1.2.  Sun Management Platform Extension

The com.sun.management package contains Sun Microsystems' platform extension to the java.lang.management API and the management interface for some other components of the platform.

4.1.1.3.  Logging Monitoring and Management Interface

The java.util.logging.LoggingMXBean interface enables to retrieve and set logging information.

4.1.1.4.  Java Management Extensions (JMX)

The JMX APIs define the architecture, design patterns, interfaces, and services for application and network management and monitoring in Java.  The APIs are based on the JMX specification

4.2.    Profiling

The Java™ Platform also includes some profiling facilities. The most popular of these "built-in" profiling tools are The -Xprof Profiler and the HPROF profiler (for uses with HPROF see also Heap Analysis Tool).

The -Xprof profiler is the HotSpot profiler. HotSpot works by running Java code in interpreted mode, while running a profiler in parallel. The HotSpot profiler looks for "hot spots" in the code, i.e. methods that the JVM spends a significant amount of time running, and then compiles those methods into native generated code.

Basically, if any method is found to be at the top of the stack more than a few times, then the application can probably benefit from having that method compiled.
§         Each thread has it's profile recorded separately, and is output separately on thread termination; there is no combined view of the application runtime.
§         Only the top runtime stack method at sample time is identified, so there is no contextual information; being told that java.lang.String.equals is a bottleneck in application is almost useless since many methods which call String.equals() is causing most of the trouble. (Note that it is fine for HotSpot, HotSpot doesn't care about context, and it just cares that String.equals() is a bottleneck, so that it knows it should spend some time compiling that method to native code.)
Only method execution is profiled; there is no object creation, garbage collection, or thread conflict profiling

CHeap Memory Issue


1.   Cause of Error

Growth in virtual address space causes this error. Leak in native code causes the virtual address space growth. The JVM throws native out of memory (native OOM) if it is not able to get any more native memory. This usually happens when the process reaches the process size limitation on that OS or the machine runs out of RAM and swap space. When this happens, the JVM handles the native OOM condition, logs a message saying that it ran out of native memory or unable to acquire memory and exits. If the JVM or any other loaded module (like libc or a third party module) doesn’t handle this native OOM situation, then the OS will send a sigabort signal to the JVM which will make the JVM exit. Usually, the JVMs will generate a core file when it gets a sigabort signal.
In this case the cause of the problem is the spring framework uses Apache Common's FileUpload library to handle multipart post requests. Version 1.0 of this library uses File.deleteOnExit() to delete temporary files. This method, however, leaks substantial amounts of memory, causing the server process to grow with each handled multipart post request. File Upload uses DiskFileUpload which in turn uses DefaultFileItem whose getTempFile() method calls File.deleteOnExit() which is known to leak memory in the native heap.

2.   Way to identify this error

Record the process virtual memory size periodically from the time the application was started until the JVM runs out of native memory. This will help to understand whether the process really hits the size limitation on that OS.

The virtual memory size can be found using these commands
prstat –L –p <PID>
pmap <PID>
ps –p <PID> -o vsz
where PID is the process Id of the weblogic managed instance. And if the growth of RSS value is significant then it means we have a native memory leak.






3.   Suggested Resolution /Recommendations

Apply the patch as suggested in the following bug report http://issues.apache.org/bugzilla/show_bug.cgi?id=27477 or migrate the version of commons file upload jar file from version 1.0 to 1.2.


4.   Recommendations


Some of the web sites of interest in this regard are as follows





Tuesday, December 22, 2009

Monitor the Oracle tables and index tablespaces

Here are my thoughts on keeping track on oracle tables and indexes growth.
  1. Collecting growth data for Oracle Tables and Indexes
create table perfstat.stats$tab_stats
(
   snap_time       date,
   server_name     varchar2(20),
   db_name         varchar2(9),
   tablespace_name varchar2(40),
   owner           varchar2(40),
   table_name      varchar2(40),
   num_rows        number,
   avg_row_len     number,
   next_extent     number,
   extents         number,
   bytes           number
)
tablespace perfstat
storage (initial 1m next 1m maxextents unlimited)
;

drop table perfstat.stats$idx_stats;


create table perfstat.stats$idx_stats
(
   snap_time         date,
   server_name       varchar2(20),
   db_name           varchar2(9),
   tablespace_name   varchar2(40),
   owner             varchar2(40),
   index_name        varchar2(40),
   clustering_factor number,
   leaf_blocks       number,
   blevel            number,
   next_extent       number,
   extents           number,
   bytes             number
)
tablespace perfstat
storage (initial 1m next 1m maxextents unlimited)
;


drop index
   perfstat.tab_stat_date_idx;

create index
   perfstat.tab_stat_date_idx
on
   perfstat.stats$tab_stats
( snap_time )
tablespace perfstat
storage (initial 1m next 1m maxextents unlimited)
;


drop index
   perfstat.idx_stat_date_idx;
create index
   perfstat.idx_stat_date_idx
on
   perfstat.stats$idx_stats
( snap_time )
tablespace perfstat
storage (initial 1m next 1m maxextents unlimited)
;


The following script can be executed once each week to analyze the table and indexes and collect the table and index data. Note that we must set the oratab file location and pass the proper ORACLE_SID when executing this script:

--****************************************************************
-- Now we grab the index statistics
--****************************************************************
 
-- add analyze and table collection commands here
 
insert into perfstat.stats\$idx_stats
(
   select
      SYSDATE,
      lower('${host}'),
      lower('${ORACLE_SID}'),
      i.tablespace_name,
      i.owner,
      i.index_name,
      i.clustering_factor,
      i.leaf_blocks,
      i.blevel,
      s.next_extent,
      s.extents,
      s.bytes
   from dba_indexes  i,
        dba_segments s,
        dba_tables   t
   where
      i.table_name = t.table_name
   and
      segment_name = index_name
   and
      s.tablespace_name = i.tablespace_name
   and  
      s.owner = i.owner
   and
      i.owner not in ('SYS','SYSTEM')
--   and
--      t.num_rows > 1000
);
Note that this script also has commented out code to restrict the population of rows to tables that contain more than 1,000 rows. This is because the DBA may only be interested in collecting statistics on the most active tables within their database.
The following reports are designed to show the DBA changes within the status of individual objects and the overall space usage for the database as a whole. For example, reports can be run against the stats$tab_stats and stats$idx_stats tables to show the total number of bytes allocated within individual tablespaces within the database.
column old_bytes format 999,999,999
column new_bytes format 999,999,999
column change    format 999,999,999

select
   new.index_name,
   old.bytes                old_bytes,
   new.bytes                new_bytes,
   new.bytes - old.bytes    change
from
   stats$idx_stats old,
   stats$idx_stats new
where
   old.index_name = new.index_name
and
   new.bytes > old.bytes
and
   new.bytes - old.bytes > 10000
and
   to_char(new.snap_time, 'YYYY-MM-DD') =
          (select max(to_char(snap_time,'YYYY-MM-DD')) from stats$idx_stats)
and
   to_char(old.snap_time, 'YYYY-MM-DD') =
           (select max(mydate) from d1)
and
   new.index_name not like 'STATS$%'
order by
   new.bytes-old.bytes desc
;

Note that this report is sequenced so that the tables with the most growth appear at the top of the report.

  1. Use Coalesce and deallocate unused space
Oracle notes that the "deallocate unused space" clause is used to to explicitly deallocate unused space at "the end" of a segment and makes that space available for other segments within the tablespace. 

alter table xxx deallocate unused space;
alter index xxx deallocate unused space;

Internally, Oracle deallocates unused space beginning from the end of the objects (allocated space) and moving downwards toward the beginning of the object, continuing down until it reaches the high water mark (HWM).  For indexes, "deallocate unused space" coalesces all leaf blocks within same branch of b-tree, and quickly frees up index leaf blocks for use.

  1. If we start using block sizes “the amount of logical reads has been reduced in half simply by using the new 16K tablespace and accompanying 16K data cache.”.
  2. Use
    1. Bitmap indexes - Bitmap indexes are used where an index column has a relatively small number of distinct values (low cardinality). These are super-fast for read-only databases, but are not suitable for systems with frequent updates.
    2. B-tree indexes - This is the standard tree index that Oracle has been using since the earliest releases.
    3. Bitmap join indexes - This is an index structure whereby data columns from other tables appear in a multi-column index of a junction table. This is the only create index syntax to employ a SQL-like from clause and where clause
  1. Limit the Number of Indexes for Each Table
  2. Drop Indexes That Are No Longer Required
  3. Estimate Index Size and Set Storage Parameters
    1. The maximum size of a single index entry is approximately one-half the data block size.
    2. Storage parameters of an index segment created for the index used to enforce a primary key or unique key constraint can be set in either of the following ways:
                                                               i.      In the ENABLE ... USING INDEX clause of the CREATE TABLE or ALTER TABLE statement
                                                             ii.      In the STORAGE clause of the ALTER INDEX statement
  1. Consider Parallelizing Index Creation
  2. Specify the Tablespace for Each Index
  3. Consider Creating Indexes with NOLOGGING
  4. Consider Costs and Benefits of Coalescing or Rebuilding Indexes
    1. Improper sizing or increased growth can produce index fragmentation. To eliminate or reduce fragmentation, you can rebuild or coalesce the index.
Rebuild Index
Coalesce Index
Quickly moves index to another tablespace
Cannot move index to another tablespace
Higher costs: requires more disk space
Lower costs: does not require more disk space
Creates new tree, shrinks height if applicable
Coalesces leaf blocks within same branch of tree
Enables you to quickly change storage and tablespace parameters without having to drop the original index.
Quickly frees up index leaf blocks for use.

Integrate Hibernate and Quartz (Hibernate alone)

In my project I had a scenario where in I had to integrate Quartz with plain Hibernate for some of the Reports to be scheduled as a batch processing.
When I started integrating I had 2 options either to go with EJBTimer (or) Quartz.

I choose Quartz since I was little more familiar with that.
But when I started the integration I had lot of issues.
Here are some of them. I tried googling but of no luck.

Tried to integrate Quartz with Hibernate (no SPRING). This is the exact implementation


  • I had a stateless Session Bean which calls the job to insert / update a record into the database.This job then internally invokes the Hibernate Sessions to do this.
  • But the problem here is when I do the same way I get an exception stating that "UserTransaction" not bound.
  • I changed the property in the Quartz.properties org.quartz.scheduler.wrapJobExecutionInUserTransaction = true but then also I was not able to synchronize my transactions with that of the hibernate transactions.
  • I some how read it in the post that the file "UserTransactionHelper.java" in the package org.quartz.ee.jta has the following lines of code

    public static final String DEFAULT_USER_TX_LOCATION =    "java:comp/UserTransaction";     
    which needs to be changed to
    public static final String DEFAULT_USER_TX_LOCATION = "UserTransaction";
    


  • The at last I figured out that we need the following properties to be set

        org.quartz.scheduler.wrapJobExecutionInUserTransaction = true
        org.quartz.scheduler.userTransactionURL=UserTransaction
    

    Here is the out an out way to integrate Hibernate Sessions with Quartz.
     SchedulerFactory schedFactory = null;
     Scheduler schedulerObj = null;
     JobDetail jobDetail = null;
     SimpleTrigger trigger = null;
     

  • Download the jar file quartz-all-1.6.5.jar
  • Update the quartz.properties inside this jar file with the 2 properties

        org.quartz.scheduler.wrapJobExecutionInUserTransaction = true
        org.quartz.scheduler.userTransactionURL=UserTransaction
     
  • From the stateless Session Bean
       // First we must get a reference to a scheduler
       schedFactory = new StdSchedulerFactory();
       schedulerObj = schedFactory.getScheduler();
       // computer a time that is on the next round milli second of a second
       Date runTime = TriggerUtils.getNextGivenSecondDate(new Date(), 2);
        // define the job and tie it to our TestUnitJob class
       jobDetail = new JobDetail("QuartzUnitRolesJob","QuartzUnitRolesJob", 
                                  QuartzUnitJob.class);
       // Trigger the job to run on the next round minute
         trigger = new SimpleTrigger("QuartzUnitRolesTrigger","QuartzUnitRolesGroup", 
                                     runTime);
       // pass initialization parameters into the job
         jobDetail.getJobDataMap().put("moduleName", JobTypes.ACL);
       // Tell quartz to schedule the job using our trigger
          schedulerObj.scheduleJob(jobDetail, trigger);
           logger.error(jobDetail.getFullName() + " will run at: " + runTime);
       // Start up the scheduler
            logger.error("------- Started Scheduler -----------------");
            schedulerObj.start();
       // wait long enough so that the scheduler as an opportunity to run the job!
           logger.error("------- Waiting 2 seconds... -------------");
            try {
         // wait 2 seconds to show jobs
            Thread.sleep(2000L);
          // executing...
            } catch (Exception e) {
              } // shut down the scheduler
           logger.error("------- Shutting Down ---------------------");
           schedulerObj.shutdown(true);
           logger.error("------- Shutdown Complete -----------------");
     schedFactory = null;
     schedulerObj = null;
     jobDetail = null;
     trigger = null;
     
  • I created an ENUM to ensure that we can use one job and use the same Quartz job for multiple purpose
    For example
        public enum JobTypes {
                    TEST_UNIT, ACL;
        @Override
           public String toString() {
           switch (this) {
             case TEST_UNIT: {
                 return "testUnit";
             }
             case ACL : {
              return "acl";
             }
             default:{
                 return "Unknown Job Type";
             }
            } // end of switch
    } // end of method
        } // end of class
        
  • As you can see the concept of ENUM is a powerful mechanism. I leave the choice to either use them or use the JobContext and create multiple Quartz Jobs
  • Create the Job
    public class QuartzUnitJob implements Job
        public class QuartzUnitJob implements Job {
          private JobTypes moduleName;
     public JobTypes getModuleName() {
      return moduleName;
     }
     public void setModuleName(JobTypes moduleName) {
      this.moduleName = moduleName;
     }
            public void execute(JobExecutionContext context) throws JobExecutionException {
      JobDataMap data = context.getJobDetail().getJobDataMap();
      JobTypes status = (JobTypes) data.get("moduleName");
                switch (status) {
                            case TEST_UNIT: {
                                        dotask1();
                                         break;
                            }
                            case ACL: {
                                        findAllRoles();
                                        break;
                            }
                 }// close of switch
     } // close of execute method
           private List findAllRoles() { 
             // your business logic goes here
           }
                } // close of class
                
    
  • Now if you go back to point(3) and see the line where I have invoked JobDatMap
    jobDetail.getJobDataMap().put("moduleName", JobTypes.ACL); 
    
  • we can understand that the combination of ENUM and JobDataMap is pretty powerful technique.
  • This ensures that I do not need to create multiple Jobs but still can achieve the same Quartz success.

  • Here are some additional thoughts on the integration
  • Clustering

    Clustering currently only works with the JDBC-Jobstore (JobStoreTX or JobStoreCMT). Features include job fail-over (if the JobDetail's "request recovery" flag is set to true) and load-balancing.

  • JTA Transactions

    As explained in the "JobStores" section of this document, JobStoreCMT allows Quartz scheduling operations to be performed within larger JTA transactions.
    Jobs can also execute within a JTA transaction (UserTransaction) by setting the "org.quartz.scheduler.wrapJobExecutionInUserTransaction" property to "true".
    With this option set, a a JTA transaction will begin() just before the Job's execute method is called, and commit() just after the call to execute terminates.

    For more information on the technical guide here is a good link for the same
    Quartz

  • Friday, December 4, 2009

    Struts and internationalization

    Struts Internationalization (i18n) can be done with some handy modifications in our existing application. We have to know the two Internationalization (i18n) components that are packaged with the Struts Framework. 
    
    1. The first of these components, which is managed by the application Controller, is a Message class that references a resource bundle containing Locale-dependent strings.
    2. The second Internationalization (i18n) component is a JSP custom tag, , which is used in the View layer to present the actual strings managed by the Controller.
    Here are the steps I would like to outline for internationalizing your application using Struts
    First thing we will require for Internationalization (i18n) is a set of simple Java properties files.
    
    1. Each file contains a key/value pair for each message that you expect your application to present, in the language appropriate for the requesting client.
    2. This property file contains the key/value pairs for the default language of your application. The naming format for this file is ResourceBundleName.properties.
    3. An example of this default file, using English as the default language, would be ApplicationResources.properties.
    4. Define a properties file for each language that your application will use.
    5. This file must follow the same naming convention as the default properties file, except that it must include the two-letter ISO language code of the language that it represents.
    Example of this naming convention
    i.            For an German-speaking client would be ApplicationResources_de.properties 
    ii.            For an French-speaking client would be ApplicationResources_fr.properties
    iii.            For an Italian-speaking client would be ApplicationResources_it.properties
    iv.            For an Spanish-speaking client would be ApplicationResources_es.properties 
    Now add the respective entries in each properties files you require.
    
    1. After you define all of the properties files for your application, you need to make Struts aware of them.
    2. It is achieved by adding a sub-element to the struts-config.xml file.
    3. Copy all the resource bundles into the application classpath, /WEB-INF/classes/example,
    4. and then use the package path plus the base file name as the value of the subelement.

    5. The following snippet shows an example of using the subelement to configure a resource bundle, using the properties files described above
    
    
    Use JSP custom tag, 
    , which is used to present the actual strings that have been loaded by the Controller.
    
    1. To use the , we must first deploy the bean tag library, which contains the tag.
    2. Deploying a tag library is a very simple process that requires only the addition of a new entry in the web.xml file of the Web application using the bean library.
    3. Here is an example of this entry:
    
    /WEB-INF/struts-bean.tld
    /WEB-INF/struts-bean.tld
     
    
    1. Check that the struts-bean.tld file is copied to the /WEB-INF/ folder.
    2. Now your tag and how it is configured for use.
    3. Copy all of the properties files to the /WEB-INF/classes/example directory and add an application subelement,
    
                    
                     
                     
    
                    
                    
                    
    
    
    Modify the index.jsp file index.jsp
    <%@ page language="java" %>
    <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
    <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> 
    
    
    Sample Struts Application
    
    
    
    
    :
    Modify the diplayname.jsp
    <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> 
    
    
    Sample Struts Display Name
    
    
    
    <%= request.getAttribute("NAME") %> !!
    Some of the finer things which needs to be noted are as follows
    1. The correct entry in struts-config.xml would be:
    2. All the resource bundles SHOULD BE in WEB-INF\classes
    3. Execute method SHOULD always have
    LanguageForm lf =(LanguageForm) form;
    Locale locale = new Locale(lf.getLanguage()); 
    HttpSession session = request.getSession(true);
    super.setLocale(request, locale);
    
    internationalization the value of the buttons