Index

1. Using JPEL

1.1 Problem

1.2 Static Model

1.3 Dynamic Model

1.4 Comparing with Properties

1.5 Comparing with ResourceBundles

1.6 Comparing with JakartaCommons

1.7 Comparing with JConfig

1.8 Comparing with Preferences API

2. Format files

2.1 XML format

2.2 Combining multiple formats

2.2 Adding new formats

3. New types of operators

3.1 Implement the interface java.language.Expression

3.2 Defining the parameter file

 

1. Using JPEL

In this section we present how JPEL can replace other commons parametrization mechanism and how it can be used to built a software with self-setup at runtime when configuration changes.To undestand how JPEL can replace other parametrization mechanisms we`ll define a problem to be solved by all mechanisms.

 

1.1 Problem

Problem is setup up a cache LRU. To use a cache LRU (Least Recently Used) probably you will have to setup up two parameters: (i) the max number of objects allowed (ii) and the number of objects to be removed after a cache overload. We want the removal number to be 20% of the max number. The goal is permit to the user of this cache change the values as wanted, without changing code. It`s also desirable to permit this change occurs while system is still running without the need of a restart.

The Java cache interface definition could be:

Cache.java

public interface Cache {
    /**
    * Ajusta o tamanho máximo da cache.
    * @param maxSize Tamanho máximo.
    */
    void setSize(int maxSize);

    /**
    * Ajusta o número de elementos p/remoção quando houver estouro.
    * @param flushSize Número de elementos p/remoção.
    */
    void setFlush(int flushSize);

    /**
     * Após o ajuste dos atributos da cache, este método inicializa
     * os recursos que a cache utiliza.
     */
    void init();

    /**
     * Adiciona um objeto a cache.
     * @param key Chave de acesso ao objeto.
     * @param value Objeto alvo.
     */
    void put(Object key, Object value);

    /**
     * Retorna um objeto da cache.
     * @param key Chave de acesso ao objeto.
     * @return O objeto armazenado.
     */
    Object get(Object key);

    /**
     * Remove um elemento da cache.
     * @param key Chave do objeto que será removido
     * @return O objeto que foi removido.
     */
    Object remove(Object key);
}

 

1.2 Using JPEL static model 

We call static model the parametrization that occurs while system is in the inicialization process, it means, it happens only one time in software execution lifetime. All other parametrizations mechanisms work according this model.

To permit software parametrization without changing code, we have to remove any decision about parameter values from code to parameter files. Bellow we show the files used by JPEL according to the JPEL specific file syntax. The syntax was softly modified from version 1.0 to make description easier to write.
cache.jpel util.jpel
# cache.jpel
# Arquivo de parametrização de uma cache.
# inclui o arquivo util.jpel
include "util.jpel" as MATH

# Define um módulo que armazena os 
#  parâmetros de uma cache de objetos.
module CACHE
{
  #Tamanho máximo permitido para 
  # qualquer cache de objetos. 1K objetos
  MAX = MATH.pow(2,10);
  #Tamanho da cache de objetos. 
  # 10% do valor máximo.
  SIZE = MATH.div(CACHE.MAX,10);
  #Numero de objeto que deve ser removido 
  # quando a cache atinge o limite de 
  # objetos. 20% do tamanho da cache.
  FLUSH = CACHE.SIZE * 0.2;
}
# util.jpel
# Arquivo biblioteca de funções

# Define uma divisão entre inteiros, 
# x é o dividendo y é o divisor.
div (x,y) =  x / y;

# Adiciona o operador de potenciação
native pow (base,expoente) =
  jpel.language.operators.ExpressionPow;

A shorten version of the file above could be:
cache.jpel util.jpel
include "util.jpel" as MATH
module CACHE
{
  MAX = MATH.pow(2,10);
  SIZE = MATH.div(CACHE.MAX,10);
  FLUSH = MATH.SIZE * 0.2; 
}
div (x,y) = x / y;

native pow (base,expoente) = 
  jpel.language.operators.ExpressionPow;

Now we show the Java code which uses the parameters file describe before.

MainJpelStatic.java

import jpel.resolver.ConfigurationBuilder;
import jpel.resolver.ConfigurationException;
import jpel.resolver.StaticConfiguration;

public class MainJpelStatic {

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        try {
            StaticConfiguration par;
            par = ConfigurationBuilder.staticConfiguration(args);
            int size = par.getInt("CACHE.SIZE");
            int flush = par.getInt("CACHE.FLUSH");
            cache.setSize(size);
            cache.setFlush(flush);
        }
        catch(ConfigurationException exc) {
            System.out.println("Erro no ajuste da cache. "+exc.getMessage());
            return;
        }
        cache.init();
        // <código do sistema que usa a cache>
        //...
    }
}

Notice that the 20% relation is defined in parameter file not in code, even better, the parameters values are not described by means of absolute values. When changes are need, it won`t be necessary recalculate all values and change absolute values into parameter files.

 

1.3 Using JPEL dynamic model

We call dynamic model the approach that permits to the software make parameters setup after inicialization process, it means, within the software execution lifetime as many times as necessary.

Here the profile used is the same os the static model, only the code is changed to the code bellow:

MainJpelDynamicSimple.java

import jpel.resolver.ConfigurationBuilder;
import jpel.resolver.ConfigurationException;
import jpel.resolver.DynamicConfiguration;
import jpel.resolver.Policy;
import jpel.resolver.PolicyActivateOnChange;
import jpel.resolver.PolicyException;
import jpel.resolver.PolicyListener;
import jpel.resolver.PolicyListenerReload;

public class MainJpelDynamicSimple {

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        try {
            DynamicConfiguration par;
            par = ConfigurationBuilder.dynamicConfiguration(args);
            par.bind(cache,"setSize",int.class,"CACHE.SIZE");
            par.bind(cache,"setFlush",int.class,"CACHE.FLUSH");
            par.bind(cache,"init");
            par.execute();

            // politica de verificação de reajuste
            PolicyListener listener = new PolicyListenerReload();
            Policy onChange = new PolicyActivateOnChange();
            ((PolicyActivateOnChange)onChange).setPeriod(10000);
            onChange.addPolicyListener(listener);
            onChange.addConfiguration(par);
            onChange.start();
        }
        catch(ConfigurationException exc) {
            System.out.println("Erro no ajuste da cache. "+exc.getMessage());
            return;
        }
        catch(PolicyException exc) {
            System.out.println("Erro no ajuste da cache. "+exc.getMessage());
            return;
        }
        // <código do sistema que usa a cache>
        //...
    }
}

 In this approach there are listeners to execute parametrization reload when changes are detected. The coding process change to permit store the parametrization history using the methods bind.

In both approaches we can identify: (i) an object to hold parametrization information - par -; (ii) a target to be set up - par; (iii) a method that must be called - setSize; (iv) the method type - int.class; and the parameter name - CACHE.SIZE. To execute dynamic parametrization a history with this informations is collected at inicialization process and reexecuted when parameters change.

Language BNF

To write applications profiles you must use the above BNF as reference.

In the following sections we show how this problem could be solved using a group of the most known parametrization mechanisms. The adicional informations well be pending for while.

 

1.4 Using Properties

The solution using JVM -D<parameters> option is:

MainPropertiesJVM.java

public class MainPropertiesJVM {

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        String strSize = System.getProperty("CACHE.SIZE");
        if( strSize == null ) { // ausencia do parametro
            System.out.println("Falta o parametro CACHE.SIZE");
            return;
        }
        int size = 0;
        try { size = Integer.parseInt(strSize); } // valor errado
        catch(NumberFormatException nfe) {
            System.out.println("Parametro CACHE.SIZE inválido.");
            return;
        }
        String strFlush = System.getProperty("CACHE.FLUSH");
        if( strFlush == null ) { // ausencia do parametro
            System.out.println("Falta o parametro CACHE.SIZE");
            return;
        }
        int flush = 0;
        try { flush = Integer.parseInt(strFlush); } // valor errado
        catch(NumberFormatException nfe) {
            System.out.println("Parametro CACHE.FLUSH inválido.");
            return;
        }
        cache.setSize(size);
        cache.setFlush(flush);
        cache.init();
        // <código do sistema que usa a cache>
        //...
    }
}

Using property files we have:

cache.properties

# Arquivo de propriedades

#Tamanho máximo permitido para qualquer cache de objetos.
CACHE.MAX=1024

#Tamanho da cache de objetos. 10% do valor máximo.
CACHE.SIZE=102

#Numero de objeto que deve ser removido quando a cache atinge 
#o limite de objetos. 20% do tamanho da cache.
CACHE.FLUSH=20

 

MainPropertiesFile.java

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class MainPropertiesFile {

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        Properties prop = new Properties();
        try { // lendo os parâmetros
            prop.load(new FileInputStream(args[0]));
        }
        catch (IOException exc) {
            System.out.println("Problemas lendo os parâmetros."+
                               exc.getMessage());
            return;
        }
        String strSize = prop.getProperty("CACHE.SIZE");
        if( strSize == null ) { // ausencia do parametro
            System.out.println("Falta o parametro CACHE.SIZE");
            return;
        }
        int size = 0;
        try { size = Integer.parseInt(strSize); } // valor errado
        catch(NumberFormatException nfe) {
            System.out.println("Parametro CACHE.SIZE inválido.");
            return;
        }
        String strFlush = prop.getProperty("CACHE.FLUSH");
        if( strFlush == null ) { // ausencia do parametro
            System.out.println("Falta o parametro CACHE.FLUSH");
            return;
        }
        int flush = 0;
        try { flush = Integer.parseInt(strFlush); } // valor errado
        catch(NumberFormatException nfe) {
            System.out.println("Parametro CACHE.FLUSH inválido.");
            return;
        }
        cache.setSize(size);
        cache.setFlush(flush);
        cache.init();
        // <código do sistema que usa a cache>
        //...
    }
}

 

 

1.5 Using ResourceBundles

 

ResCode .java

import java.util.ListResourceBundle;

public class ResCode extends ListResourceBundle {
    static final Object[][] contents = new Object[][]{
        { "CACHE.SIZE", new Integer(102) },
        { "CACHE.FLUSH", new Integer(20) }
    };
    public Object[][] getContents() {
        return contents;
    }
}

 

MainResourceBundlesCode.java

import java.util.ResourceBundle;

public class MainResourceBundlesCode {

    static ResourceBundle res = ResourceBundle.getBundle("ResCode");

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        Object objSize = res.getObject("CACHE.SIZE");
        //falta ou erro
        if( objSize == null || !(objSize instanceof Integer) ) {
            System.out.println("CACHE.SIZE deve ser do tipo Integer");
            return;
        }
        int size = ((Integer)objSize).intValue();
        Object objFlush = res.getObject("CACHE.FLUSH");
        //falta ou erro
        if( objFlush == null || !(objFlush instanceof Integer) ) {
            System.out.println("CACHE.FLUSH deve ser do tipo Integer");
            return;
        }
        int flush = ((Integer)objFlush).intValue();
        cache.setSize(size);
        cache.setFlush(flush);
        cache.init();
        // <código do sistema que usa a cache>
        //...
    }
}

 

 

1.6 Using JakartaCommons

The parameters file is the same used at Properties example. The cod is given bellow.

MainJakartaCommons.java

import java.io.IOException;
import java.util.NoSuchElementException;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.PropertiesConfiguration;

public class MainJakartaCommons {

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        try {
            Configuration prop = new PropertiesConfiguration(args[0]);
            int size = prop.getInt("CACHE.SIZE");
            int flush = prop.getInt("CACHE.FLUSH");
            cache.setSize(size);
            cache.setFlush(flush);
        }
        catch(IOException exc) {
            System.out.println("Erro lendo a configuração."+exc.getMessage());
            return;
        }
        catch(NoSuchElementException exc) {
            System.out.println("Parametro não encontrado."+exc.getMessage());
            return;
        }
        cache.init();
        // <código do sistema que usa a cache>
        //...
    }
}

 

 

1.7 Using JConfig

The configuration file:

cache.jconfig

<?xml version="1.0"?>
<properties> 
  <category name="general">
    <property name="CACHE.MAX" value="1024" />
    <property name="CACHE.SIZE" value="102" />
    <property name="CACHE.FLUSH" value="20" />
  </category>  
</properties>

The static model of JConfig can lead to the code:

MainJConfigStatic.java

import java.io.File;
import org.jconfig.ConfigurationManager;
import org.jconfig.ConfigurationManagerException;

public class MainJConfigStatic {

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        ConfigurationManager cfgmgr = ConfigurationManager.getInstance();
        try {
          cfgmgr.load(new File(args[0]));
          String strSize = cfgmgr.getProperty("CACHE.SIZE");
          if( strSize == null ) { // ausencia do parametro
              System.out.println("Falta o parametro CACHE.SIZE");
              return;
          }
          int size = 0;
          try { size = Integer.parseInt(strSize); } // valor errado
          catch(NumberFormatException nfe) {
              System.out.println("Parametro CACHE.SIZE inválido.");
              return;
          }
          String strFlush = cfgmgr.getProperty("CACHE.FLUSH");
          if( strFlush == null ) { // ausencia do parametro
              System.out.println("Falta o parametro CACHE.SIZE");
              return;
          }
          int flush = 0;
          try { flush = Integer.parseInt(strFlush); } // valor errado
          catch(NumberFormatException nfe) {
              System.out.println("Parametro CACHE.FLUSH inválido.");
              return;
          }
          cache.setSize(size);
          cache.setFlush(flush);
        }
        catch( ConfigurationManagerException exc) {
            System.out.println("Erro no ajuste da cache. "+exc.getMessage());
            return;
        }
        cache.init();
        // <código do sistema que usa a cache>
        //...
    }
}

The dynamic model of JConfig also permits self-setup at runtime, here is a sample code. Compare with the JPEL and make your own chooice.

MainJConfigDynamic.java

import java.io.File;
import org.jconfig.ConfigurationManager;
import org.jconfig.ConfigurationManagerException;
import org.jconfig.event.FileListener;
import org.jconfig.event.FileListenerEvent;

public class MainJConfigDynamic {

    public static class ListenerImpl implements FileListener {
        private Cache cache;
        public ListenerImpl(Cache cache) {
            this.cache = cache;
        }
        public void fileChanged( FileListenerEvent evt ) {
            ConfigurationManager cfgmgr = ConfigurationManager.getInstance();
            try {
                cfgmgr.load(evt.getFile());
                // código replicado no main
                String strSize = cfgmgr.getProperty("CACHE.SIZE");
                if( strSize == null ) { // ausencia do parametro
                    System.out.println("Falta o parametro CACHE.SIZE");
                    return;
                }
                int size = 0;
                try { size = Integer.parseInt(strSize); } // valor errado
                catch(NumberFormatException nfe) {
                    System.out.println("Parametro CACHE.SIZE inválido.");
                    return;
                }
                String strFlush = cfgmgr.getProperty("CACHE.FLUSH");
                if( strFlush == null ) { // ausencia do parametro
                    System.out.println("Falta o parametro CACHE.SIZE");
                    return;
                }
                int flush = 0;
                try { flush = Integer.parseInt(strFlush); } // valor errado
                catch(NumberFormatException nfe) {
                    System.out.println("Parametro CACHE.FLUSH inválido.");
                    return;
                }
                cache.setSize(size);
                cache.setFlush(flush);
            }
            catch(ConfigurationManagerException exc) {
                System.out.println("Erro no ajuste da cache. "+exc.getMessage());
                return;
            }
            cache.init();
        }
    }

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        ConfigurationManager cfgmgr = ConfigurationManager.getInstance();
        try {
          cfgmgr.load(new File(args[0]));
          cfgmgr.setAutoReload(true);
          cfgmgr.addFileListener(new ListenerImpl(cache));
          String strSize = cfgmgr.getProperty("CACHE.SIZE");
          if( strSize == null ) { // ausencia do parametro
              System.out.println("Falta o parametro CACHE.SIZE");
              return;
          }
          int size = 0;
          try { size = Integer.parseInt(strSize); } // valor errado
          catch(NumberFormatException nfe) {
              System.out.println("Parametro CACHE.SIZE inválido.");
              return;
          }
          String strFlush = cfgmgr.getProperty("CACHE.FLUSH");
          if( strFlush == null ) { // ausencia do parametro
              System.out.println("Falta o parametro CACHE.SIZE");
              return;
          }
          int flush = 0;
          try { flush = Integer.parseInt(strFlush); } // valor errado
          catch(NumberFormatException nfe) {
              System.out.println("Parametro CACHE.FLUSH inválido.");
              return;
          }
          cache.setSize(size);
          cache.setFlush(flush);
        }
        catch( ConfigurationManagerException exc) {
            System.out.println("Erro no ajuste da cache. "+exc.getMessage());
            return;
        }
        cache.init();
        // <código do sistema que usa a cache>
        //...
        //try { Thread.sleep(100000); } catch (InterruptedException ex) {}
    }
}

 

 

1.8 Using Preferences API

XML file used to configure software using Preferences API.

cache.preferences

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE preferences SYSTEM 'http://java.sun.com/dtd/preferences.dtd'>
<preferences EXTERNAL_XML_VERSION="1.0">
  <root type="user">
    <map>
      <entry key="CACHE.MAX" value="1024" />
      <entry key="CACHE.SIZE" value="102" />
      <entry key="CACHE.FLUSH" value="20" />
    </map>
  </root>
</preferences>

Java file that use the XML above.

MainPreferencesFile.java

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.util.prefs.Preferences;
import java.util.prefs.InvalidPreferencesFormatException;

public class MainPreferencesFile {

    public static void main(String[] args) {
        Cache cache = new CacheImpl();
        try {
            InputStream is = new BufferedInputStream(new FileInputStream(args[0]));
            Preferences.importPreferences(is);
            Preferences pref = Preferences.userRoot();
            int size = pref.getInt("CACHE.SIZE",1024);
            int flush = pref.getInt("CACHE.FLUSH",102);
            cache.setSize(size);
            cache.setFlush(flush);
        }
        catch (FileNotFoundException exc) {
            System.out.println("Arquivo não encontrado."+exc.getMessage());
        }
        catch (InvalidPreferencesFormatException exc) {
            System.out.println("Arquivo com formato errado."+exc.getMessage());
        }
        catch (IOException exc) {
            System.out.println("Erro de leitura."+exc.getMessage());
        }
        cache.init();
        // <código do sistema que usa a cache>
        //...
    }
}

 

 

 

2. Format files

Here we show that the same program written using JPEL can handle files in different formats, showing how XML can be used, and also how you can write your own parsers to integration within JPEL.

 

2.1 XML Format

Beside the specific JPEL format given at section 1, we can use XML to describe profiles applications bellow we can see the same cache example written in a XML syntax.
cache.xml

util.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<configuration>
  <description><![CDATA[
     cache.xml
     Arquivo de parametrização de uma cache.
  ]]></description>

  <include id="util.xml" as="MATH">
    <description><![CDATA[
       inclui o arquivo util.xml
    ]]></description>
  </include>

  <module id="CACHE">
    <description><![CDATA[
       Define um módulo que armazena os 
        parâmetros de uma cache de objetos.
    ]]></description>

    <function id="MAX">
      <description><![CDATA[
        Tamanho máximo permitido para 
         qualquer cache de objetos. 1K objetos
      ]]></description>
      <value><![CDATA[
        MATH.pow(2,10)
      ]]></value>
    </function>
    
    <function id="SIZE">
      <description><![CDATA[
        Tamanho da cache de objetos. 
         10% do valor máximo.
      ]]></description>
      <value><![CDATA[
        MATH.div (CACHE.MAX,10)
      ]]></value>
    </function>
    
    <function id="FLUSH">
      <description><![CDATA[
        Numero de objeto que deve ser removido 
         quando a cache atinge o limite de 
         objetos. 20% do tamanho da cache.
      ]]></description>
      <value><![CDATA[
        CACHE.SIZE * 0.2
      ]]></value>
    </function>
  </module>
</configuration>
<?xml version="1.0" encoding="ISO-8859-1"?>
<configuration>
  <description><![CDATA[
     util.xml
     Arquivo biblioteca de funções
  ]]></description>

  <function id="div" args="(x,y)">
    <description><![CDATA[
       Define uma divisão entre inteiros, 
       x é o dividendo y é o divisor.
    ]]></description>
    <value><![CDATA[
      x / y
    ]]></value>
  </function>
  
  <native id="pow" args="(base,expoente)">
    <description><![CDATA[
       Adiciona o operador de potenciação
    ]]></description>
    <value><![CDATA[
      jpel.language.operators.ExpressionPow
    ]]></value>
  </native>
</configuration>

You can simply XML sintax using an XML tags file indicator, or using the XML reader constructor which receives a XMLConstants instance. Above you will see an XML file tag definition, used to generate the new XML file. To run applications using another XML tag file definition use the JVM parameter -Dxml.constants indicating the appropriate file, o create a specific XML reader with XMLConstants set up.

If you want to convert an XML file to another one, create an XML reader instance with the appropriate XMLConstants and an XML writer with the target XMLConstants, use reader to read old XML and the writer to write the new format.

source_constants.lib

target_constants.lib

CONFIGURATION :: configuration
INCLUDE       :: include
MODULE        :: module
FUNCTION      :: function
NATIVE        :: native
NAME          :: id
FORMAL        :: args
DESCRIPTION   :: description
VALUE         :: value
AS            :: as
CONFIGURATION :: config
INCLUDE       :: inc
MODULE        :: group
FUNCTION      :: par
NATIVE        :: nat
NAME          :: id
FORMAL        :: arg
DESCRIPTION   :: des
VALUE         :: val
AS            :: as

Converting the formats we have got:
cache.xml

util.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<config>
  <des><![CDATA[
     cache.xml
     Arquivo de parametrização de uma cache.
  ]]></des>

  <inc id="util.xml" as="MATH">
    <des><![CDATA[
       inclui o arquivo util.xml
    ]]></des>
  </inc>

  <group id="CACHE">
    <des><![CDATA[
       Define um módulo que armazena os 
        parâmetros de uma cache de objetos.
    ]]></des>

    <par id="MAX">
      <des><![CDATA[
        Tamanho máximo permitido para 
         qualquer cache de objetos. 1K objetos
      ]]></des>
      <val><![CDATA[
        MATH.pow(2,10)
      ]]></val>
    </par>
    
    <par id="SIZE">
      <des><![CDATA[
        Tamanho da cache de objetos. 
         10% do valor máximo.
      ]]></des>
      <val><![CDATA[
        MATH.div (CACHE.MAX,10)
      ]]></val>
    </par>
    
    <par id="FLUSH">
      <des><![CDATA[
        Numero de objeto que deve ser removido 
         quando a cache atinge o limite de 
         objetos. 20% do tamanho da cache.
      ]]></des>
      <val><![CDATA[
        CACHE.SIZE * 0.2
      ]]></val>
    </par>
  </group>
</config>
<?xml version="1.0" encoding="ISO-8859-1"?>
<config>
  <des><![CDATA[
     util.xml
     Arquivo biblioteca de funções
  ]]></des>

  <par id="div" arg="(x,y)">
    <des><![CDATA[
       Define uma divisão entre inteiros, 
       x é o dividendo y é o divisor.
    ]]></des>
    <val><![CDATA[
      x / y
    ]]></val>
  </par>
  
  <nat id="pow" arg="(base,expoente)">
    <des><![CDATA[
       Adiciona o operador de potenciação
    ]]></des>
    <val><![CDATA[
      jpel.language.operators.ExpressionPow
    ]]></val>
  </nat>
</config>

 

 

2.2 Combining multiple formats

Another option into JPEL is the possibility of using diferent file formats together. i.e The cache profile could be a the compositions of JPEL syntax and XML.

cache.jpel

util.xml

# cache.jpel
# Arquivo de parametrização de uma cache.
# inclui o arquivo util.jpel
include "util.jpel" as MATH

# Define um módulo que armazena os 
#  parâmetros de uma cache de objetos.
module CACHE
{
  #Tamanho máximo permitido para 
  # qualquer cache de objetos. 1K objetos
  MAX = MATH.pow(2,10);
  #Tamanho da cache de objetos. 
  # 10% do valor máximo.
  SIZE = MATH.div(CACHE.MAX,10);
  #Numero de objeto que deve ser removido 
  # quando a cache atinge o limite de 
  # objetos. 20% do tamanho da cache.
  FLUSH = CACHE.SIZE * 0.2;
}
<?xml version="1.0" encoding="ISO-8859-1"?>
<configuration>
  <description><![CDATA[
     util.xml
     Arquivo biblioteca de funções
  ]]></description>

  <function id="div" args="(x,y)">
    <description><![CDATA[
       Define uma divisão entre inteiros, 
       x é o dividendo y é o divisor.
    ]]></description>
    <value><![CDATA[
      x / y
    ]]></value>
  </function>
  
  <native id="pow" args="(base,expoente)">
    <description><![CDATA[
       Adiciona o operador de potenciação
    ]]></description>
    <value><![CDATA[
      jpel.language.operators.ExpressionPow
    ]]></value>
  </native>
</configuration>

 

 

2.3 Adding new formats

To add new file formats your must:

2.3.1 Implement interfaces jpel.tree.NodeReader/ jpel.tree.NodeWriter

The basic idea of the format independency is the existence of a declaration tree model:

cache.jpel

util.xml

+ DeclarationInclude(cache.jpel)
   |
   + DeclarationInclude(util.jpel)
   |
   + DeclarationModule(CACHE)
      |
      + DeclarationFunction(MAX,(),MATH.pow(2,10))
      |
      + DeclarationFunction(SIZE,(),MATH.div (CACHE.MAX,10) )
      |
      + DeclarationFunction(FLUSH,(),CACHE.SIZE * 0.2)
+ DeclarationInclude(util.xml)
   |
   + DeclarationFunction(div,(x,y),x/y)
   |
   + DeclarationNative(pow,(base,expoente),
      jpel.language.operators.ExpressionPow)

The objetive of the NodeReader is to extract from the given file this tree of declarations objects. When the tree is ok, we have build a JPEL reader. The writer must be able to write this tree in the same input format. Thus we have brought a new format file to JPEL framework. To undertand better this process you can see the NodeReaderJpel.jj JavaCC file and NodeReaderXML.java, as well as NodeWriterJpel.java and NodeWriterXML.java files.

 

2.3.2 Add information to framework file

The final step to add the new Reader/Writer into the framework is to edit the files readers.lib and writers.lib inside de language module jar file to add an entry with the names of the reader/writer Java classes.

Now add the implementations classes to your classpath and use the format files you have defined. Important: there is no need to modify the applications using JPEL.

 

 

3. Adding new types of operators

An useful feature is the possibility of extending the types/operators defined into the language. Above we will see how a new parameter type can be added to the configuration file, the example is a ODBC Connection defined at the configuration file.

3.1 Implement the interface java.language.Expression

Everything that can be executed and produce a result we call Expression, defined by the Java interface jpel.language.Expression. There are helper classes to simplify the implementing process, one of this classes is the superclasse AbstractExpression and the ExpressionObject to hold the result shown in the example bellow.
ExpressionConnection.java
package jpel.language.extensions;

import java.sql.Connection;
import java.sql.DriverManager;

import jpel.language.AbstractExpression;
import jpel.language.Environment;
import jpel.language.ExecutionException;
import jpel.language.Expression;
import jpel.language.ExpressionList;
import jpel.language.ExpressionObject;
import jpel.language.MapReplace;
import jpel.language.StringableExpression;
import jpel.language.Type;

/**
 * Realiza uma conexão com um banco de dados.
 */
public class ExpressionConnection extends AbstractExpression {
    private Expression driver;
    private Expression url;
    private Expression user;
    private Expression password;

    public ExpressionConnection( Expression driver, Expression url,
        Expression user, Expression password ) {
        this( "con", driver, url, user, password );
    }

    public ExpressionConnection( String id, Expression driver, Expression url,
        Expression user, Expression password ) {
        super( id, Type.NATIVE );
        setDriver( driver );
        setUrl( url );
        setUser( user );
        setPassword( password );
    }

    public void setDriver( Expression driver ) {
        this.driver = driver;
    }

    public Expression getDriver() {
        return driver;
    }

    public void setUrl( Expression url ) {
        this.url = url;
    }

    public Expression getUrl() {
        return url;
    }

    public void setUser( Expression user ) {
        this.user = user;
    }

    public Expression getUser() {
        return user;
    }

    public void setPassword( Expression password ) {
        this.password = password;
    }

    public Expression getPassword() {
        return password;
    }

    public void freeVariable( ExpressionList list ) {
        return;
    }

    public Expression rebuild( MapReplace map ) {
        MapReplace novo = map.mirror();
        return new ExpressionConnection(
            driver.rebuild( novo ),
            url.rebuild( novo ),
            user.rebuild( novo ),
            password.rebuild( novo )
            );
    }

    public Expression eval( Environment env ) throws ExecutionException {
        try {
            Expression result = null;
            StringableExpression drv = ( StringableExpression )this.driver.execute( env );
            StringableExpression url = ( StringableExpression )this.url.execute(env );
            StringableExpression usr = ( StringableExpression )this.user.execute( env );
            StringableExpression pwd = ( StringableExpression )this.password.execute( env );
            Class.forName( drv.asString() );
            Connection con = DriverManager.getConnection(
                             url.asString(),
                             usr.asString(),
                             pwd.asString()
                             );
            return new ExpressionObject( con );
        }
        catch( Exception exc ) {
            throw new ExecutionException( this, exc.getMessage(), exc );
        }
    }
}

 

3.2 Defining the parameter file

To use the implemented class you must create a native reference and add the .class to you classpath. Bellow an example file is shown.

connection.jpel

{
  odbc = {
    connectionOdbc("sourceValue","userValue","passwordValue")
  }
  
  connectionOdbc (source,user,password) = {
    connection(
      "sun.jdbc.odbc.JdbcOdbcDriver",
      "jdbc:odbc:" ++ source,
      user,
      password
    )
  }
  
  native connection (driver,url,user,password) = {
    jpel.language.extensions.ExpressionConnection
  }
}

In this example we have defined a Odbc connection with:
- source = "sourceValue"
- user = "userValue"
- password = "passwordValue"

To request a connection from the application using JPEL you just need:
- Create the Configuration instance (see section Using JPEL);
- Call the method getObject() passing "odbc" reference:

Connection con = (Connection)par.getObject("odbc");

Ready! After this steps you can use the connection as a simple parameter, and the same procedure to define new types of parameters. In fact a group of component could be developed to speed up the development process.