Difference between revisions of "Data Access Layer"

From InfiniteERP Wiki
Jump to: navigation, search
(DAL Architecture)
(Update Data Access Layer dari Etendo Documentation, rebrand Etendo→InfiniteERP)
 
Line 1: Line 1:
== Overview ==
+
{{Languages}}
The Openbravo Data Access Layer (DAL) is a new development in Openbravo ERP 2.50. The goal of the DAL development is to strengthen the middle-tier in the application, i.e. to implement business logic in Java. The DAL provides the application developer with the following functionality:
+
{{BackTo|Main Page}}
  
* Type safe querying and retrieval of business objects from the database.
+
= Data Access Layer =
* A convenient API to update or create new data in the database.
+
 
* A type safe interface to update information of a business object, increased productivity by making the properties of a business object directly visible through getters and setters (in the IDE).
+
The goal of the InfiniteERP Data Access Layer (DAL) development is to strengthen the middle-tier in the application, i.e. to implement business logic in Java. The DAL provides the application developer with the following functionality:
* Transaction and context handling.
+
 
* Security and validation checking.
+
* Type-safe querying and retrieval of business objects from the database
* Automatically maps new entries in the Application Dictionary to database tables and columns.
+
* A convenient API to update or create new data in the database
* Generates Java class business objects (and their associations) on the basis of the Application Dictionary model.
+
* A type-safe interface to update information of a business object, increased productivity by making the properties of a business object directly visible through getters and setters (in the IDE)
 +
* Transaction and context handling
 +
* Security and validation checking
 +
* Automatically maps new entries in the Application Dictionary to database tables and columns
 +
* Generates Java class business objects (and their associations) on the basis of the Application Dictionary model
  
 
The DAL consists of a development-time and runtime part. The development-time part takes care of generating Java business object classes. The runtime part takes care of mapping Java classes to the database and supporting functionality such as security and validation.
 
The DAL consists of a development-time and runtime part. The development-time part takes care of generating Java business object classes. The runtime part takes care of mapping Java classes to the database and supporting functionality such as security and validation.
  
== A 'Hello World' Example ==
+
== A "Hello World" Example ==
As a first simple example let's create a new business partner group and store it in the database:
 
  
<source lang="java">
+
As a first simple example, let's create a new business partner group and store it in the database:
// create the object through the factory
 
  final Category bpg = OBProvider.getInstance().get(Category.class);
 
 
// set some values
 
bpg.setDefault(true);
 
bpg.setDescription("hello world");
 
bpg.setName("hello world");
 
bpg.setValue("hello world");
 
bpg.setActive(true);
 
  
// store it in the database
+
<code>
OBDal.getInstance().save(bpg);
+
// create the object through the factory
</source>
+
final Category bpg = OBProvider.getInstance().get(Category.class);
 +
 
 +
// set some values
 +
bpg.setDefault(true);
 +
bpg.setDescription("hello world");
 +
bpg.setName("hello world");
 +
bpg.setValue("hello world");
 +
bpg.setActive(true);
 +
 
 +
// store it in the database
 +
OBDal.getInstance().save(bpg);
 +
</code>
  
 
There are a number of things which are important to note:
 
There are a number of things which are important to note:
  
* The above code does not set an explicit '''[[Data_Access_Layer#User_Context|user context]]'''. The user context is set automatically when running the above code in Openbravo ERP. However in other environments it has to be set explicitly, see [[Data_Access_Layer#User_Context|here]] for more information.
+
* The above code does not set an explicit user context. The user context is set automatically when running the above code in InfiniteERP. However, in other environments, it has to be set explicitly see [[#User_Context|User Context]] for more information.
* There is a BPGroup class which models the data of the ''c_bp_group'' table. This class has type safe getters and setters for all the data in this table.  
+
* There is a BPGroup class which models the data of the <code>c_bp_group</code> table. This class has type-safe getters and setters for all the data in this table.
 
* A factory (the OBProvider) is used to create an instance of the BPGroup class.
 
* A factory (the OBProvider) is used to create an instance of the BPGroup class.
* The OBDal service is the main entry point into the Data Access Layer, it offers save, remove and query functionality. The OBDal API is discussed in more detail below.
+
* The OBDal service is the main entry point into the Data Access Layer; it offers save, remove and query functionality.
 +
 
 +
The code snippet above also shows that you do not need to work with SQL or JDBC to work with the data from the database. As a developer, you work directly with objects and the available data is directly visible through the getters and setters.
 +
 
 +
As a next step, let's query for the business partner group and change its description:
  
The code snippet above also shows that you don't need to work with SQL or JDBC to work with the data from the database anymore. As a developer you work directly with objects and the available data is directly visible through the getters and setters.
+
<code>
 +
// create an OBCriteria object and add a filter
 +
final OBCriteria&lt;Category&gt; obCriteria = OBDal.getInstance().createCriteria(Category.class);
 +
obCriteria.add(Restrictions.eq("name", "hello world"));
  
As a next step, lets query for the business partner group, and change its description:
+
// perform the actual query returning a typed list
 +
final List&lt;Category&gt; categories = obCriteria.list();
 +
final Category cat = categories.get(0);
  
<source lang="java">
+
// and set a new name
// create an OBCriteria object and add a filter
+
cat.setName("another hello world");
final OBCriteria<Category> obCriteria = OBDal.getInstance().createCriteria(Category.class);
+
OBDal.getInstance().save(cat);
obCriteria.add(Restrictions.eq("name", "hello world"));
+
</code>
 
// perform the actual query returning a typed list
 
final List<Category> categories = obCriteria.list();
 
final Category cat = categories.get(0);
 
 
// and set a new name
 
cat.setName("another hello world");
 
OBDal.getInstance().save(cat);
 
</source>
 
  
 
This code snippet introduced a number of new concepts:
 
This code snippet introduced a number of new concepts:
  
 
* The OBDal service is used to create an OBCriteria object.
 
* The OBDal service is used to create an OBCriteria object.
* The OBCriteria object represents the query, implements the Hibernate Criteria interface and can be used as a standard Hibernate Criteria object. The OBCriteria object also supports sorting and paging parameters. The OBCriteria API is discussed in more detail below.
+
* The OBCriteria object represents the query, implements the Hibernate Criteria interface and can be used as a standard Hibernate Criteria object. The OBCriteria object also supports sorting and paging parameters.
* The OBCriteria list method performs the actual query, it returns a type safe list of the requested objects.
+
* The OBCriteria <code>list</code> method performs the actual query; it returns a type-safe list of the requested objects.
* After changing the name of the business partner group you don't need to do an explicit save. At commit time Hibernate will automatically detect ''dirty'' objects and save those.
+
* After changing the name of the business partner group, you don't need to do an explicit save. At commit time, Hibernate will automatically detect ''dirty'' objects and save those.
 
 
This was a short introduction which shows how the DAL can be used to create, store and retrieve one (simple) business object. The rest of this document will describe the Data Access Layer functionality in more detail.
 
  
 
== DAL Architecture ==
 
== DAL Architecture ==
  
The image below shows the envisioned architecture for the data access layer in this and following releases of Infinite ERP.
+
The architecture for the Data Access Layer in InfiniteERP is implemented as follows:
 
 
 
 
[[Image:DalOverviewSmaller.jpg]]
 
 
 
 
 
This architecture is partially implemented in 3.00 and will be extended and completed in following releases:
 
  
* Runtime model (from 2.50): the runtime model is the main driver for generating the business objects and the Hibernate mapping. It's also used extensively in security, export/import and in webservices implementations.
+
* '''Runtime model''': the runtime model is the main driver for generating the business objects and the Hibernate mapping. It is also used extensively in security, export/import and in webservices implementations.
* Hibernate Mapping (from 2.50): from the runtime model the DAL (during initialization) generates a Hibernate mapping. This Hibernate mapping is used to initialize Hibernate.
+
* '''Hibernate Mapping''': from the runtime model, the DAL (during initialization) generates a Hibernate mapping. This Hibernate mapping is used to initialize Hibernate.
* Database Schema: the runtime model (actually the application dictionary) can be used to update the database schema. In 2.50 this is not available as part of the DAL but as part of the DBSourceManager product.
+
* '''Database Schema''': the runtime model (actually the Application Dictionary) can be used to update the database schema. This is not available as part of the DAL but as part of the DBSourceManager product.
* Data Access Layer (from 2.50): the Data Access Layer (DAL) provides an API to store, query and remove business objects from the database.
+
* '''Data Access Layer''': the DAL provides an API to store, query and remove business objects from the database.
* Business Model/Logic Layer (some examples in 2.50): the business model/logic layer contains the implementation of the business processes.
+
* '''Business Model/Logic Layer''': the business model/logic layer contains the implementation of the business processes.
* Business Services (not in 3.00, except for REST webservice): the service layer exposes the business logic to the outside world. In 2.50 this layer contains the REST Webservice provided by the DAL.
+
* '''Business Services''': the service layer exposes the business logic to the outside world.
  
 
The complete architecture runs inside of a context which provides security and transaction handling.
 
The complete architecture runs inside of a context which provides security and transaction handling.
  
 
== Business Object ==
 
== Business Object ==
This document uses the term business object to denote an entity and its dependent information. A business object can be a simple entity such as a currency which just has basic primitive fields. On the other hand it can also be a structure of entities, for example an order header with its order line.
 
  
A business object structure always has one business object which is the owner of all the business objects in that structure, for example for a sales order the sales order header business object is the owner of the complete structure (i.e. of the sales order lines. Another way of describing this is that the sales order lines depend on the sales order header, a sales order line can not exist without its sales order header and when a sales order header is removed also its sales order lines should be removed.
+
This document uses the term '''business object''' to denote an entity and its dependent information. A business object can be a simple entity such as a currency which just has basic primitive fields. On the other hand, it can also be a structure of entities, for example an order header with its order line.
  
The DAL uses the foreign keys from the child to parent to create the parent-child association in Java and Hibernate. '''More specifically: the foreign key columns which have the field isParent set to yes (checked/true) define the business object parent-child relations''', other foreign key columns define standard many-to-one associations. For example the foreign key field ''c_order_id'' in ''c_order_line'' is used to create the one-to-many association (in the in-memory runtime model) from ''c_order'' to ''c_order_line''. This one-to-many association is then used to generate a List<OrderLine> member in the Java Order class and a one-to-many mapping in Hibernate.
+
A business object structure always has one business object which is the owner of all the business objects in that structure. For example, for a sales order the sales order header business object is the owner of the complete structure (i.e., of the sales order lines). A sales order line cannot exist without its sales order header, and when a sales order header is removed, its sales order lines should also be removed.
  
For a reference of all business object structures see the entity model [http://wiki.openbravo.com/wiki/ERP/3.0/Developers_Guide/Reference/Entity_Model here].
+
The DAL uses the foreign keys from the child to parent to create the parent-child association in Java and Hibernate. More specifically: the foreign key columns which have the field <code>isParent</code> set to yes (checked/true) define the business object parent-child relations; other foreign key columns define standard many-to-one associations. For example, the foreign key field <code>c_order_id</code> in <code>c_order_line</code> is used to create the one-to-many association (in the in-memory runtime model) from <code>c_order</code> to <code>c_order_line</code>. This one-to-many association is then used to generate a <code>List</code> member in the Java Order class and a one-to-many mapping in Hibernate.
 +
 
 +
'''Note''': Generated one-to-many properties in the parent entity load all children in memory when invoked, so they should only be generated when the expected amount of child records is low, otherwise it might cause an <code>OutOfMemoryError</code>.
  
 
== DAL Main Interfaces ==
 
== DAL Main Interfaces ==
The DAL offers three main services to instantiate, create and query Openbravo ERP business objects: OBDal, OBCriteria and OBProvider. The service classes can all be found in the ''org.openbravo.dal.service'' package.
+
 
 +
The DAL offers three main services to instantiate, create and query InfiniteERP business objects: '''OBDal''', '''OBCriteria''' and '''OBProvider'''. The service classes can all be found in the <code>org.openbravo.dal.service</code> package.
  
 
=== OBDal ===
 
=== OBDal ===
The [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/service/OBDal.java OBDal] instance (available through ''OBDal.getInstance()'') is the main entrance point for retrieving and storing business objects in the database in a validated and secure way. It provides the following functions:
 
 
* save: stores a new business object in the database or an existing business object is updated. For existing business object it is not required to call this method as Hibernate does automatic ''dirty'' checking.
 
* get: retrieves a single business object using its id. There are two versions: one using the class name (of the generated business object) and one using the entity name.
 
* remove: delete a business object from the database, the actual database delete is done at commit time.
 
* create OBCriteria: [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/service/OBCriteria.java OBCriteria] objects are used for querying.
 
* commitAndClose and rollbackAndClose: these methods can be used to implement custom transaction handling. Normally this is done by the environment (Openbravo web container or Openbravo test).
 
 
The OBDal API makes extensive use of the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/service/OBCriteria.java OBCriteria] and [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/service/OBQuery.java OBQuery] classes to support querying.
 
  
{{AvailableFrom|3.0PR17Q2}}
+
The <code>OBDal</code> instance (available through <code>OBDal.getInstance()</code>) is the main entrance point for retrieving and storing business objects in the database in a validated and secure way. It provides the following functions:
  
The OBDal class allows to access to the [http://wiki.openbravo.com/wiki/Openbravo.properties#Read-Only_Pool '''read-only'''] database (pool). In this case, the instance must be retrieved with the ''OBDal.getReadOnlyInstance()'' method. It is important to note that if the read-only pool is not configured, this method will use the standard pool to get the database connections.
+
* '''save''': stores a new business object in the database or updates an existing business object. For existing business objects, it is not required to call this method as Hibernate does automatic ''dirty'' checking.
 +
* '''get''': retrieves a single business object using its ID. There are two versions: one using the class name (of the generated business object) and one using the entity name.
 +
* '''remove''': delete a business object from the database; the actual database delete is done at commit time.
 +
* '''create OBCriteria''': OBCriteria objects are used for querying.
 +
* '''commitAndClose''' and '''rollbackAndClose''': these methods can be used to implement custom transaction handling. Normally, this is done by the environment (InfiniteERP web container or InfiniteERP test).
  
{{AvailableFrom|3.0PR18Q3}}
+
The OBDal API makes extensive use of the OBCriteria and [[#OBQuery|OBQuery]] classes to support querying.
Once the read-only pool is configured, there are two ways to override ''OBDal.getReadOnlyInstance()'' behavior:
 
* Creating a record in ''Data Pool Selection'', which associates a database pool with a particular report.
 
* Using the ''Default DB Pool used by reports'' Preference, which defines the default pool returned by ''OBDal.getReadOnlyInstance()'' if no entry in Data Pool Selection is made for the current Process.
 
  
See [[Read-Only_Pool|Read-Only Pool]] for more information about the configuration of this pool.
+
The OBDal class allows accessing the read-only database (pool). In this case, the instance must be retrieved with the <code>OBDal.getReadOnlyInstance()</code> method.
  
 
=== OBCriteria ===
 
=== OBCriteria ===
The [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/service/OBCriteria.java OBCriteria] class implements the [http://docs.jboss.org/hibernate/core/3.5/reference/en/html/querycriteria.html Hibernate Criteria] interface. It extends the standard Hibernate Criteria functionality for filtering on active, client and organization. In addition it offers convenience methods to set orderby and to perform count actions.
 
 
Summarizing, the OBCriteria object supports all Hibernate Criteria features:
 
  
* Setting the where-clause of a filter using the Hibernate Criterion concept.
+
The OBCriteria class implements the [https://docs.jboss.org/hibernate/core/3.5/reference/en/html/querycriteria.html Hibernate Criteria] interface. It extends the standard Hibernate Criteria functionality for filtering on active, client and organization. In addition, it offers convenience methods to set order by and to perform count actions.
* Setting paging parameters such as first row and maximum number of results.
 
* Setting order by on the query.
 
* Specifying joins for performance reasons.
 
* Performing counts, average etc.
 
  
For more information, see the [http://docs.jboss.org/hibernate/core/3.5/reference/en/html/querycriteria.html Hibernate Manual] on Criteria queries.
+
Summarizing, the OBCriteria object supports all Hibernate Criteria features:
  
The functionality of OBCriteria is illustrated with a number of code snippets:
+
* Setting the where-clause of a filter using the Hibernate Criterion concept
 +
* Setting paging parameters such as first row and maximum number of results
 +
* Setting order by on the query
 +
* Specifying joins for performance reasons
 +
* Performing counts, average, etc.
  
 
An OBCriteria instance is created as follows:
 
An OBCriteria instance is created as follows:
  
<source lang="java">
+
<code>
final OBCriteria obc = OBDal.getInstance().createCriteria(Currency.class);
+
final OBCriteria&lt;Currency&gt; obc = OBDal.getInstance().createCriteria(Currency.class);
</source>
+
</code>
  
 
Query on the name property:
 
Query on the name property:
  
<source lang="java">
+
<code>
obc.add(Restrictions.eq("name", "testname"));
+
obc.add(Restrictions.eq("name", "testname"));
</source>
+
</code>
  
 
Restrictions.eq is an instance of the Hibernate Criterion class. The Hibernate Criterion concept is an expression language coded in Java supporting most commonly used expressions (and, or, equal, not-equal, in, between, etc.).
 
Restrictions.eq is an instance of the Hibernate Criterion class. The Hibernate Criterion concept is an expression language coded in Java supporting most commonly used expressions (and, or, equal, not-equal, in, between, etc.).
Line 148: Line 136:
 
Setting a descending order by on the name property:
 
Setting a descending order by on the name property:
  
<source lang="java">
+
<code>
obc.addOrderBy("name", false);
+
obc.addOrderBy("name", false);
</source>
+
</code>
  
Or another one: order by the name property of a referenced business object product:
+
Or another one: order by the name property of a referenced business object:
  
<source lang="java">
+
<code>
obc.addOrderBy("product.name", false);
+
obc.addOrderBy("product.name", false);
</source>
+
</code>
  
Set some paging parameters, return 10 objects, beginning with the 100th:
+
Set paging parameters, return 10 objects, beginning with the 100th:
  
<source lang="java">
+
<code>
obc.setFirstResult(100);
+
obc.setFirstResult(100);
obc.setMaxResults(10);
+
obc.setMaxResults(10);
</source>
+
</code>
  
Also return inactive objects (as a default only active objects are returned):
+
Also return inactive objects (as a default, only active objects are returned):
  
<source lang="java">
+
<code>
obc.setFilterOnActive(false);
+
obc.setFilterOnActive(false);
</source>
+
</code>
  
 
Count the number of Currency objects in the database:
 
Count the number of Currency objects in the database:
  
<source lang="java">
+
<code>
final int bpGroupCount = obc.count();
+
final int count = obc.count();
</source>
+
</code>
 
 
Retrieve the list of Business Objects:
 
 
 
<source lang="java">
 
final List<BPGroup> bpgs = obc.list();
 
</source>
 
 
 
Get a specific currency:
 
 
 
<source lang="java">
 
final OBCriteria<Currency> obc = OBDal.getInstance().createCriteria(Currency.class);
 
obc.add(Restrictions.eq("isoCode", "USD"));
 
final List<Currency> cs = obc.list();
 
final Currency c = cs.get(0);
 
</source>
 
  
 
=== OBQuery ===
 
=== OBQuery ===
The [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/service/OBQuery.java OBQuery] class is an extension of the [http://www.hibernate.org/hib_docs/reference/en/html/queryhql.html Hibernate Query] object. It extends the standard Hibernate Query functionality for filtering on active, client and organization.
 
  
The OBQuery object is created through the ''OBDal.createQuery'' method. The first argument of the createQuery is a class or an entity name, the second argument is the where-clause. The where-clause can be a simple one:
+
The OBQuery class is an extension of the [https://docs.jboss.org/hibernate/orm/3.5/reference/en/html/queryhql.html Hibernate Query] object. It extends the standard Hibernate Query functionality for filtering on active, client and organization.
  
name='test'
+
The OBQuery object is created through the <code>OBDal.createQuery</code> method. The first argument of createQuery is a class or an entity name, the second argument is the where-clause.
  
or one which also declares an alias:
+
<code>
 +
final OBQuery&lt;Category&gt; obQuery = OBDal.getInstance().createQuery(Category.class, "name='testname' or searchKey='testvalue'");
 +
final List&lt;Category&gt; bpgs = obQuery.list();
 +
</code>
  
as ol where ol.order.id='abc'
+
=== OBProvider ===
  
In code:
+
InfiniteERP business objects should '''not''' be instantiated directly using the <code>new</code> operator. Instead, the <code>OBProvider</code> class should be used to create an instance of the required business object. The OBProvider is located in the <code>org.openbravo.base.provider</code> package and can be retrieved using the method <code>OBProvider.getInstance()</code>.
  
<source lang="java">
+
<code>
    final OBQuery<Category> obQuery = OBDal.getInstance().createQuery(Category.class,
+
final Category bpg = OBProvider.getInstance().get(Category.class);
        "name='testname' or searchKey='testvalue'");
 
    final List<Category> bpgs = obQuery.list();
 
</source>
 
  
=== OBProvider ===
+
// The ENTITYNAME constant is created by the business object generation logic
Openbravo business objects should '''not''' be instantiated directly using the new operator. Instead the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/provider/OBProvider.java OBProvider] class should be used to create an instance of the required business object. The OBProvider is located in the ''org.openbravo.base.provider'' package and can be retrieved using the method OBProvider.getInstance(). The OBProvider offers methods to instantiate using a class name or using an entity name. Some code examples:
+
final BPGroup bpg = (BPGroup) OBProvider.getInstance().get(BPGroup.ENTITYNAME);
 +
</code>
  
<source lang="java">
+
== BaseOBObject ==
  final Category bpg = OBProvider.getInstance().get(Category.class);
 
 
// The ENTITYNAME constant is created by the business object generation logic
 
final BPGroup bpg = (BPGroup)OBProvider.getInstance().get(BPGroup.ENTITYNAME);
 
</source>
 
  
== Openbravo Business Objects ==
+
All InfiniteERP business objects inherit from the <code>BaseOBObject</code> class. This class is located in the <code>org.openbravo.base.structure</code> package.
The DAL generates, instantiates and uses Openbravo ERP business objects. This part of the development manual describes their structure and main interfaces.
 
 
 
=== BaseOBObject ===
 
All Openbravo ERP business object inherit from the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/structure/BaseOBObject.java BaseOBObject] class. This class is located in the ''org.openbravo.base.structure'' package.  
 
  
 
The BaseOBObject class offers the following functionality:
 
The BaseOBObject class offers the following functionality:
  
* Direct access (called the dynamic API) to all the properties and values of the data within the business object through the get(String propertyName) and set((String propertyName, Object value) methods. These methods are particularly useful when working with generic functions such as security and logging.  
+
* '''Direct access''' (called the dynamic API) to all the properties and values of the data within the business object through the <code>get(String propertyName)</code> and <code>set(String propertyName, Object value)</code> methods. These methods are particularly useful when working with generic functions such as security and logging.
 
* Access to the Entity describing the type and properties of the business object.
 
* Access to the Entity describing the type and properties of the business object.
 
* Security and validation checks when getting and setting values.
 
* Security and validation checks when getting and setting values.
* Access to the id of the object through the getId method.
+
* Access to the ID of the object through the <code>getId</code> method.
* Access to the identifier of the object: the getIdentifier method of the BaseOBObject uses the identifier properties of the object to create a displayable title for that object.  
+
* Access to the identifier of the object: the <code>getIdentifier</code> method of the BaseOBObject uses the identifier properties of the object to create a displayable title for that object.
  
The section on runtime model and dynamic API below, gives an example on how the dynamic API and the runtime model can be used.
+
=== Generated Business Object Classes ===
  
As all Openbravo ERP business objects should extend this class, it is safe to cast an object to this class when required.
+
At development time, the DAL will generate business object classes for each table defined in the Application Dictionary. This is done as part of the <code>compile.complete</code> Ant task or can be done separately through the <code>generate.entities</code> Ant task. These generated classes are the classes which are normally used by a developer because they offer compile-time-checked typed access to properties.
  
=== Generated Business Object Classes ===
+
The generated classes extend BaseOBObject and offer typed wrapper getters and setters around the generic set and get method of the parent BaseOBObject class:
At development time the DAL will generate business object classes for each table defined in the application dictionary. This is done as part of the compile.complete ant tasks or can be done separately through the generate.entities ant task. These generated classes are the classes which are normally used by a developer because they offer compile-time-checked typed access to properties. The generated classes are created in the ''src-gen'' folder in the Openbravo ERP development project and are part of the ''org.openbravo.base.model'' package and its subpackages.
 
  
The generated classes extend the BaseOBObject and offer typed wrapper getters and setters around the generic set and get method of the parent BaseOBObject class:
+
<code>
 +
public String getName() {
 +
    return (String) get("name");
 +
}
  
<source lang="java">
+
public void setName(String name) {
public String getRecord() {
+
    set("name", name);
  return (String) get("record");
+
}
}
+
</code>
 
public void setRecord(String record) {
 
  set("record", record);
 
}
 
</source>
 
  
In addition the generated Java classes set default values (in the constructor) and have a static ENTITYNAME variable which must be used when it is required to refer directly to an entity name of an entity.
+
In addition, the generated Java classes set default values (in the constructor) and have a static <code>ENTITYNAME</code> variable which must be used when it is required to refer directly to an entity name of an entity.
  
 
=== Entity, Property and Column Naming ===
 
=== Entity, Property and Column Naming ===
  
The data access layer uses names defined in the Application Dictionary for different purposes:
+
The Data Access Layer uses names defined in the Application Dictionary for different purposes:
* xml tag names in REST web services and import export
 
* class names of generated Entity classes
 
* member names of members of generated Entity classes
 
* to detect that a certain Entity implements/supports a certain interface (see [[Data_Access_Layer#Important_Interfaces |here]])
 
  
The application dictionary historically allows many different types of names (also ones that are illegal for xml/java). Therefore the data access layer applies specific conversion logic to always ensure that names are allowed and unique for xml/Java.
+
* XML tag names in REST web services and import/export
 +
* Class names of generated Entity classes
 +
* Member names of members of generated Entity classes
 +
* To detect that a certain Entity implements/supports a certain interface
  
For a full listing of all entity and property names see the [http://wiki.openbravo.com/wiki/Data_Model Data Model Reference] chapters.
+
The Application Dictionary historically allows many different types of names (also ones that are illegal for XML/Java). Therefore, the data access layer applies specific conversion logic to always ensure that names are allowed and unique for XML/Java.
  
 
==== Entity Naming ====
 
==== Entity Naming ====
An entity (corresponds to a table in Openbravo ERP) has different names, relevant for different situations:
 
  
* A table name (stored in ''AD_Table.tablename'') which is the database table name in the physical database.
+
An entity (corresponds to a table in InfiniteERP) has different names, relevant for different situations:
* An entity name (present in ''AD_Table.name'') which is a globally unique name. It corresponds to the XML tag name used for that entity. It is for example used in [[XML_REST_Web_Services | REST webservices]] and client export/import.
 
* A Java class name (stored in ''AD_Table.classname''), the classname is used when generating the Java business object. It is unique within the data package of the table.
 
  
Each table (i.e. entity) in Openbravo ERP belongs to a data package. A data package has a Java package field which defines the Java package in which the entity Java class is generated.
+
* A '''table name''' (stored in <code>AD_Table.tablename</code>) which is the database table name in the physical database.
 +
* An '''entity name''' (present in <code>AD_Table.name</code>) which is a globally unique name. It corresponds to the XML tag name used for that entity.
 +
* A '''Java class name''' (stored in <code>AD_Table.classname</code>), the class name is used when generating the Java business object. It is unique within the data package of the table.
  
{{(!)| AD_Table.name shouldn't contain blank spaces. If the name of the table contains spaces, the process of building the entity name will remove those spaces, e.g. 'My Table' will be converted to 'MyTable'.}}
+
Each table (i.e., entity) in InfiniteERP belongs to a data package. A data package has a Java package field which defines the Java package in which the entity Java class is generated.
 +
 
 +
'''Warning''': <code>AD_Table.name</code> should not contain blank spaces. If the name of the table contains spaces, the process of building the entity name will remove those spaces (e.g., <code>My Table</code> will be converted to <code>MyTable</code>).
  
 
==== Property Naming ====
 
==== Property Naming ====
For property naming the logic is slightly different. The property naming logic has two distinct steps: 1) first determine an initial property name, and 2) correct/convert this property name.
 
  
The initial property name is determined as follows:
+
For property naming, the logic has two distinct steps:
  
* Properties for standard primitive type (varchar, numeric, etc.) and foreign key columns: for these properties the value in ''AD_Column.name'' is used as the start of the property name calculation.
+
1. First determine an initial property name, and
* Properties which model a list of child entities: the so-called one-to-many or list properties. For example the Order class has a property ''OrderLineList''. The initial name of this property is set as follows:
+
2. Correct/convert this property name.
** (common case) if the column name on the other side (from the child to the parent) is the same as the primary key column name of the parent then use the entity name of the child plus the suffix 'List'. For example the c_orderline table has a foreign key c_order_id to the c_order table. This results in a property orderLineList in the Order entity.
 
** (non-common case) if the column name, which points from the child to the parent, is different than the primary key column name of the parent then the following naming rule is used: target entity name + "_" + referenced property name + "List". For example, if the c_orderLine table has a column: c_orderheader_id with property name orderHeader then the resulting list property in the Order would be: OrderLine_orderHeaderList.  
 
  
Then next the property name generation performs the following steps:
+
The initial property name is determined as follows:
  
* Spaces are removed and used to camelcase, for example the name 'Enable in Cash' is converted to: EnableInCash (note the uppercased I).
+
* For standard primitive type (varchar, numeric, etc.) and foreign key columns: the value in <code>AD_Column.name</code> is used as the start of the property name calculation.
* Underscores are removed and used to camelcase: for example the name 'C_Poc_email_ID' is translated to CPocEmailID.
+
* For properties which model a list of child entities (one-to-many): if the column name on the other side (from the child to the parent) is the same as the primary key column name of the parent, then use the entity name of the child plus the suffix "List" (e.g., <code>OrderLineList</code> in the Order entity).
* 'illegal' characters are removed: only characters from a to z and A to Z and numbers (not as prefix) are maintained, so 'G/L Item' will be translated to 'GLItem'.
 
* The first character is lowercased: AccountingFact is converted to accountingFact.
 
  
==== Property Naming and Supported interfaces ====
+
Next, the property name generation performs the following steps:
  
The data access layer automatically detects that an entity supports a certain interface by analyzing the name of the properties of that entity. '''It is therefore very important to be precise in naming of properties'''. The naming of properties and the interfaces supported by specific properties are discussed in the next section ([[Data_Access_Layer#Important_Interfaces|Important Interfaces]]).
+
* Spaces are removed and used to camelcase (e.g., "Enable in Cash" → <code>EnableInCash</code>).
 +
* Underscores are removed and used to camelcase (e.g., "C_Poc_email_ID" → <code>CPocEmailID</code>).
 +
* "Illegal" characters are removed: only characters from a to z and A to Z and numbers (not as prefix) are maintained.
 +
* The first character is lowercased (e.g., <code>AccountingFact</code> → <code>accountingFact</code>).
  
 
=== Important Interfaces ===
 
=== Important Interfaces ===
 +
 
The generated classes implement a set of interfaces which can be used to check if a certain instance of a class has specific functionality available:
 
The generated classes implement a set of interfaces which can be used to check if a certain instance of a class has specific functionality available:
  
* [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/structure/ClientEnabled.java ClientEnabled]: flags an object as having a getClient/setClient method and as an object which is stored by Client. '''Note''': Openbravo automatically detects that a table (==Entity) implements this interface if it has a column with the name: client (defined in the ad_column.name field). When this interface is implemented/detected then the client property is used to automatically filter objects (on the readable clients) when querying and to do security checks when persisting an object.  
+
* '''[ClientEnabled]''': flags an object as having a <code>getClient</code>/<code>setClient</code> method and as an object which is stored by Client. InfiniteERP automatically detects that a table implements this interface if it has a column with the name <code>client</code>.
* [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/structure/OrganizationEnabled.java OrganizationEnabled]: an object implementing this interface has getOrganization/setOrganization methods. '''Note''': Openbravo automatically detects that a table (==Entity) implements this interface if it has a column with the name: organization (defined in the ad_column.name field). When this interface is implemented/detected then the organization property is used to automatically filter objects (on the readable organizations) when querying and to do security checks when persisting an object.  
+
* '''[OrganizationEnabled]''': an object implementing this interface has <code>getOrganization</code>/<code>setOrganization</code> methods. InfiniteERP automatically detects this if it has a column with the name <code>organization</code>.
* [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/structure/ActiveEnabled.java ActiveEnabled]: an object implementing this interface has an active flag (boolean) which can be reached through the isActive/setActive methods. '''Note''': Openbravo automatically detects that a table (==Entity) implements this interface if it has a column with the nameactive (defined in the ad_column.name field). When this interface is detected/implemented then the active field is used in automatic filtering of objects when querying through the data access layer.
+
* '''[ActiveEnabled]''': an object implementing this interface has an active flag (boolean) which can be reached through the <code>isActive</code>/<code>setActive</code> methods. InfiniteERP automatically detects this if it has a column with the name <code>active</code>.
* [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/structure/Traceable.java Traceable]: a Traceable object has audit information: created and updated (date fields), and createdby and updatedby (contain a User). This audit information is reachable through corresponding accessors. '''Note''': Openbravo automatically detects that a table (==Entity) implements this interface if it has the following columns (all are required): creationDate, created, updated, updatedBy (set in the ad_column.name field). '''See specifically the name of the date of creation property, it should be: creationDate'''. When this interface is implemented/detected then the data access layer will automatically set audit fields when an object is persisted.
+
* '''[Traceable]''': a Traceable object has audit information: created and updated (date fields), and createdBy and updatedBy (contain a User). InfiniteERP automatically detects this if it has the following columns (all are required): <code>creationDate</code>, <code>created</code>, <code>updated</code>, <code>updatedBy</code>.
 +
 
 +
=== Client, Organization and Audit Information ===
  
=== Client and organization and Audit information ===
 
 
The above interfaces are used by the DAL when an object is saved for the first time or updated in the database:
 
The above interfaces are used by the DAL when an object is saved for the first time or updated in the database:
  
* a ClientEnabled object for which the Client is not set will get the current Client of the user (present in the user context).
+
* A ClientEnabled object for which the Client is not set will get the current Client of the user (present in the user context).
* an OrganizationEnabled object for which the Organization is not set will get the current Organization of the user (present in the user context).
+
* An OrganizationEnabled object for which the Organization is not set will get the current Organization of the user (present in the user context).
* a Traceable object: when saved for the first time the created, createdby, updated and updatedby are set. When an object is updated then the updated and updatedby are set.
+
* A Traceable object: when saved for the first time, the <code>created</code>, <code>createdBy</code>, <code>updated</code> and <code>updatedBy</code> are set. When an object is updated, then the <code>updated</code> and <code>updatedBy</code> are set.
  
So, a developer does not need to explicitly set this information in a new or existing object. Note for the data access layer to detect that a table supports the above interfaces, the column names (AD_Column.name) need to adhere to specific standards, see [[Data_Access_Layer#Property_Naming_and_Supported_interfaces|here]] for more information.
+
So, a developer does not need to explicitly set this information in a new or existing object.
  
=== Creating a new instance of a Business Object ===
+
=== Creating a New Instance of a Business Object ===
  
A business object may never be created using the Java new operator. All business objects should be created using the [https://code.openbravo.com/erp/devel/pi/file/d128f84ed546/src/org/openbravo/base/provider/OBProvider.java OBProvider] factory class:
+
A business object may never be created using the Java <code>new</code> operator. All business objects should be created using the OBProvider factory class:
<source lang="java">
+
 
// create the object through the factory
+
<code>
  final Category bpg = OBProvider.getInstance().get(Category.class);  
+
final Category bpg = OBProvider.getInstance().get(Category.class);
</source>
+
</code>
  
 
Hibernate will detect that a business object is new when:
 
Hibernate will detect that a business object is new when:
* the id of the business object is not set
 
* when the flag newOBObject is set to true explicitly
 
  
So if you want to create a new business object with a specific id (by calling setId(...)) then you explicitly need to call businessObject.setNewOBObject(true). Otherwise, Hibernate will throw an exception ('count of batch update operation....').
+
* The ID of the business object is not set
 +
* When the flag <code>newOBObject</code> is set to true explicitly
 +
 
 +
So, if you want to create a new business object with a specific ID (by calling <code>setId(...)</code>), then you explicitly need to call <code>businessObject.setNewOBObject(true)</code>. Otherwise, Hibernate will throw an exception.
  
 
== User Context ==
 
== User Context ==
The DAL operates within a user context. The user context is implemented in the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/core/OBContext.java OBContext] class in the ''org.openbravo.dal.core''. The OBContext is initialized using a userId. On the basis of this userId the OBContext computes the role, clients, organizations and accessible entities. This information is used by the DAL for security checking and automatic filtering on client and organization.
 
  
The OBContext is stored as a ThreadLocal variable and the DAL always assumes that there is one available. The OBContext can be retrieved using the call ''OBContext.getOBContext()''.
+
The DAL operates within a '''user context'''. The user context is implemented in the <code>OBContext</code> class in the <code>org.openbravo.dal.core</code> package. The OBContext is initialized using a userId. On the basis of this userId, the OBContext computes the role, clients, organizations and accessible entities. This information is used by the DAL for security checking and automatic filtering on client and organization.
 +
 
 +
The OBContext is stored as a ThreadLocal variable and the DAL always assumes that there is one available.
  
Normally a developer can assume that there is always a user context available. The following sections discuss this in more detail.
+
=== User Context in a Running InfiniteERP Instance ===
  
=== User Context in a running Openbravo Instance ===
+
When the code runs inside an InfiniteERP application, the user context is always set. This is done through a request filter (the <code>DalRequestFilter</code> in the <code>org.openbravo.dal.core</code> package).
When the code runs inside of an Openbravo ERP web application then the user context is always set. This is done through a request filter (the ''DalRequestFilter'' in the ''org.openbravo.dal.core'' package). This request filter ensures that the OBContext ThreadLocal is set to the current user (of Openbravo), puts the OBContext in the http session and cleans up the OBContext when the thread ends (as the thread may be reused by another user).
 
  
=== User Context in a Test environment ===
+
=== User Context in a Standalone Situation ===
The DAL uses a base class for its test cases (see topic Testing further below). This base class takes care of setting the user context when running tests.
 
  
=== User Context in a Standalone situation ===
+
The user context can also be set by calling <code>OBContext.setOBContext(userId)</code> with a userId which exists in the <code>ad_user</code> table.
The user context can also be set by calling ''OBContext.setOBContext(userId)'' with a userId which exists in the ad_user table. This will setup a user context and place it in the OBContext ThreadLocal member to be used by the DAL.
 
  
 
=== Administrator Mode ===
 
=== Administrator Mode ===
As was just mentioned, DAL operates within a user context, and provides automatic security checking mechanisms to prevent the user from accessing data which according to the Openbravo Security Model he shouldn't have access to.
 
 
However, in most cases, the piece of code developed is contained within an object which in itself automatically provides part of the security checking, in particular the entity access. For example, if the piece of code is part of a process which is called from a button, the user will only be able to click that button if he is already in the window (and therefore, has access to that entity). The same happens when the code is part of a callout (the callout will only be fired if the user is already in the window).
 
  
The OBContext provides an Administrator Mode which can be used to perform administrative actions even if the user does not have enough privileges. This mode bypasses the Entity Access checking, and it also doesn't filter by Client or Organization.
+
The OBContext provides an Administrator Mode which can be used to perform administrative actions even if the user does not have enough privileges. This mode bypasses the Entity Access checking and it does not filter by Client or Organization.
  
An additional Administrator Mode is provided, which bypasses the Entity Access checking, but it does filter by Client Organization. '''It is encouraged to use this restricted Administrator Mode, as it conveniently filters by Client and Organization, something that is usually needed on Business Logic code'''.
+
An additional Administrator Mode is provided, which bypasses the Entity Access checking but does filter by Client Organization.
  
 
The syntax to activate the restricted Admin Mode:
 
The syntax to activate the restricted Admin Mode:
  
<source lang="java">try {
+
<code>
  OBContext.setAdminMode(true);
+
try {
 +
    OBContext.setAdminMode(true);
  
  // do administrative things here
+
    // do administrative things here
  
 
} finally {
 
} finally {
  OBContext.restorePreviousMode();
+
    OBContext.restorePreviousMode();
 
}
 
}
</source>
+
</code>
  
In some cases, it is necessary, though, to prevent also client/organization check, this can be done using <code>OBContext.setAdminMode(false)</code>.
+
In some cases, it is necessary to prevent also client/organization check; this can be done using <code>OBContext.setAdminMode(false)</code>.
  
Note that the calls to setAdminMode/restorePreviousMode are balanced, meaning that for each call to setAdminMode there is also exactly one call to restorePreviousMode. If in one request the number of calls to setAdminMode is unequal to the number of calls to restorePreviousMode then a warning is displayed: ''Unbalanced calls to enableAsAdminContext and resetAsAdminContext''.
+
'''Note''': The calls to setAdminMode/restorePreviousMode are balanced, meaning that for each call to setAdminMode there is also exactly one call to restorePreviousMode.
 
 
==== Cross Organization Reference Administrator Mode ====
 
 
 
{{(!)|Contents of this section are available starting from '''3.0PR16Q3'''}}
 
 
 
Validation for referenced object's organization in columns [[#Cross_Organization_References|supporting it]] can be bypassed by using a special Administrator mode: <code>setCrossOrgReferenceAdminMode</code>. Restoring previous mode is done by <code>restorePreviousCrossOrgReferenceMode</code>. Similarly to standard admin mode, calls must be balanced for this mode, independently from standard admin mode.
 
 
 
<source lang="java">
 
  OBContext.setCrossOrgReferenceAdminMode();
 
  try {
 
 
 
    // cross organization references are allowed here
 
 
 
  } finally {
 
    OBContext.restorePreviousCrossOrgReferenceMode();
 
  }
 
</source>
 
  
 
== Transaction and Session ==
 
== Transaction and Session ==
Openbravo DAL implements the so-called [https://www.hibernate.org/43.html open-session-view] pattern. With a slight variation that the DAL will automatically create a Hibernate Session and start a transaction when the first data access takes place (if none existed). So not when the HTTP requests begins. The Session and Transaction are placed in a ThreadLocal and re-used for subsequent data access actions in the same thread.
 
  
An important thing to be aware of is that normally all database actions are flushed to the database when the session is committed. In a web environment this is at the end of the http request. To perform flush on demand call the ''OBDal.getInstance().flush()'' method.
+
The DAL implements the so-called ''open-session-view'' pattern. With a slight variation: the DAL will automatically create a Hibernate Session and start a transaction when the first data access takes place (if none existed). So not when the HTTP request begins. The Session and Transaction are placed in a ThreadLocal and re-used for subsequent data access actions in the same thread.
  
Normally a developer does not need to explicitly commit or rollback a Session or Transaction:
+
An important thing to be aware of: normally all database actions are flushed to the database when the session is committed. In a web environment, this is at the end of the HTTP request. To perform flush on demand, call the <code>OBDal.getInstance().flush()</code> method.
  
* Within Openbravo ERP: the [https://www.hibernate.org/43.html open-session-view] pattern is used, when running the code in the Openbravo ERP web application, the transaction commit and session close takes place at the end of the http request (see the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/core/DalRequestFilter.java DalRequestFilter]). If an exception occurs then a rollback is performed.
+
* Within InfiniteERP: the open-session-view pattern is used. When running the code in the InfiniteERP application, the transaction commit and session close take place at the end of the HTTP request. If an exception occurs, then a rollback is performed.
* In the Openbravo ERP test environment: The DAL base test class ([https://code.openbravo.com/erp/devel/pi/file/8051c8a30e72/src-test/org/openbravo/test/base/OBBaseTest.java OBBaseTest]) takes care of committing or rollingback transactions.
+
* In test environment: the DAL base test class takes care of committing or rolling back transactions.
* Standalone: if the code is running standalone then an explicit commit or rollback needs to be performed. This can be done through the OBDal methods: ''OBDal.getInstance().commitAndClose()'' or ''OBDal.getInstance().rollbackAndClose())''.
+
* Standalone: if the code is running standalone, then an explicit commit or rollback needs to be performed. This can be done through the OBDal methods: <code>OBDal.getInstance().commitAndClose()</code> or <code>OBDal.getInstance().rollbackAndClose()</code>.
  
'''SQLC and DAL - Beware''': the standard Openbravo ERP database access (through windows) works outside of the DAL (in 2.50 release). This means that, database access uses a different connection than the DAL. If both connections update the database then it is possible that a deadlock situations happens. So, when working/updating through both the DAL and SQLC one should always first commit/close the connection of the DAL explicitly before continuing with SQLC or the other way around.
+
'''Warning''': SQLC and DAL: The standard InfiniteERP database access (through Windows) works outside of the DAL. This means that database access uses a different connection than the DAL. If both connections update the database, then it is possible that a deadlock situation happens. So, when working/updating through both the DAL and SQLC, one should always first commit/close the connection of the DAL explicitly before continuing with SQLC, or the other way around.
  
 
== Security and Validation ==
 
== Security and Validation ==
The DAL performs many different security and validation checks. In both read and write mode. A security violation results in a [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/exception/OBSecurityException.java ''SecurityException''], a validation error in a [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/validation/ValidationException.java ''ValidationException'']. Different exceptions are thrown at different points: some Security Exceptions are thrown when reading a business object (e.g. read security violations), others are thrown when the session commits or flushes (write security violations) or when a setter is called (ValidationException).
 
  
If you need to work without this security checks, you should use the Administrator Mode, or in MP22 and later, the restricted Administrator Mode. You can find more information about them [[Data_Access_Layer#Administrator_Mode|Administrator Mode]]
+
The DAL performs many different security and validation checks, in both read and write mode. A security violation results in a <code>SecurityException</code>, a validation error in a <code>ValidationException</code>.
  
 
=== Write Access ===
 
=== Write Access ===
Write access checks are done when the OBDal save or remove methods are called or when a business object is saved by hibernate (at flush/commit). For write access the following checks are performed:
 
  
* The user must have access to a window/tab which displays the entity. See the [[Database_Model/org.openbravo.model.ad.access/AD_Window_Access|AD_Window_Access]] table.
+
Write access checks are done when the OBDal save or remove methods are called or when a business object is saved by Hibernate (at flush/commit). For write access, the following checks are performed:
* The organisation of the business object must be in the list of writable organisations of that user. Writable organisations are organisations directly linked to the role of the user.
+
 
 +
* The user must have access to a window/tab which displays the entity. See the <code>AD_Window_Access</code> table.
 +
* The organization of the business object must be in the list of writable organizations of that user.
 
* The client of the business object must be in the list of writable clients of that user.
 
* The client of the business object must be in the list of writable clients of that user.
  
If any of the above checks fails then a ''SecurityException'' is thrown.
+
If any of the above checks fails, then a <code>SecurityException</code> is thrown.
  
 
=== Read Access ===
 
=== Read Access ===
A user can only view information from his/her own clients and accessible organisations. This is ensured by the DAL by automatically adding filter criteria in the OBCriteria object.
 
  
Read access is checked for both direct read access and derived read access. Direct read access allows a user to see all the information of a certain entity. With derived read access only the active property, audit info and the id and identifier may be read by a user.
+
A user can only view information from their own clients and accessible organizations. This is ensured by the DAL by automatically adding filter criteria in the OBCriteria object.
  
Direct read access is based on the window access tables, i.e. a user can read all the information of a certain entity if the user has access to a window displaying that entity. The read access for a certain table/entity can be overridden using the ad_table_access table. The set of directly readable entities is computed by the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/security/EntityAccessChecker.java EntityAccessChecker] in the ''org.openbravo.dal.security'' package.
+
Read access is checked for both '''direct read access''' and '''derived read access''':
  
Derived read access is computed as follows: For each directly readable entity it is determined to which other entities it refers. These other entities are derived read accessible. For example if a user may directly read an invoice entity, and an invoice refers to a currency then the user has derived read access to a currency. The derived readable entities are also computed in the EntityAccessChecker.
+
* '''Direct read access''': allows a user to see all the information of a certain entity. Based on the window access tables.
 +
* '''Derived read access''': only the active property, audit info and the ID and identifier may be read by a user. For example, if a user may directly read an invoice entity, and an invoice refers to a currency, then the user has derived read access to a currency.
  
Direct and derived read access are checked at different points:
+
=== Delete Check ===
 
 
* Direct read access is checked when a method on the OBDal instance is called.
 
* Derived read access is checked when a getter is invoked or the generic get method is called.
 
  
=== Delete Check ===
 
 
A user may delete a business object if:
 
A user may delete a business object if:
  
* He/she has write access to the entity of the business object.
+
* They have write access to the entity of the business object.
* The entity is not set as not-deletable, this is defined in the ''ad_table''.
+
* The entity is not set as not-deletable, this is defined in the <code>AD_Table</code>.
  
Both security checks are done when the session is committed or flushed. See the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/dal/core/OBInterceptor.java OBInterceptor] class for more details.
+
=== Validation ===
  
=== Table Access Validation ===
 
Different tables in Openbravo ERP have different access levels. Some tables only allow information from client 0 and organisation *. Other tables allow objects from any organisation.
 
 
When a business object is saved a check is done if the client and organisation of that business object are valid for the table access level of the table. The check has been implemented in the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/validation/AccessLevelChecker.java AccessLevelChecker] in ''org.openbravo.base.validation''. This check is performed when the session commits or is flushed.
 
 
=== Validation ===
 
 
Property values are validated when the setter is called or the generic set method on the BaseOBObject is called. The following checks are performed:
 
Property values are validated when the setter is called or the generic set method on the BaseOBObject is called. The following checks are performed:
* a check is done if the instance of the value is valid for that property
 
* for String values a check is done on length or on the allowed String values (list/enumerate values)
 
* for numeric values the min and max are checked
 
* for mandatory values a check is done if the value is unequal to null
 
  
The property validation is performed by the classes in [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/validation org.openbravo.base.validation]. The validation structure is initialized when the runtime model is created. For each property a corresponding PropertyValidator is created. Different types of properties have different types of PropertyValidators.
+
* A check is done if the instance of the value is valid for that property
 +
* For String values, a check is done on length or on the allowed String values (list/enumerate values)
 +
* For numeric values, the min and max are checked
 +
* For mandatory values, a check is done if the value is unequal to null
  
 
=== Entity Organization Validation ===
 
=== Entity Organization Validation ===
A business object may only refer to other business objects which belong to the natural tree of the organization of the business object. This validation is done when a business object is saved (i.e. when the session commits/flushes).
 
  
==== Cross Organization References ====
+
A business object may only refer to other business objects which belong to the natural tree of the organization of the business object. This validation is done when a business object is saved (i.e., when the session commits/flushes).
  
{{AvailableFrom|3.0PR16Q3}}
+
'''Cross Organization References''': Entity Organization validation can be bypassed if the foreign key column that references to the other object is marked as ''Allow Cross Organization Reference'' and the context is in [[#Administrator_Mode|Cross Organization Reference Administrator Mode]].
  
Entity Organization validation can be bypassed if the following conditions are all fulfilled:
+
== DAL Support for Database Views ==
  
* The foreign key column that references to the other object is marked as ''Allow Cross Organization Reference''. This can be set in ''Tables and Columns > Column'' tab.
+
The DAL supports views in practically the same way as normal tables defined in the Application Dictionary. This means that:
* Context is in [[#Cross_Organization_Reference_Administrator_Mode|Cross Organization Reference Administrator Mode]].
 
  
===== Cross Organization references in UI =====
+
* Database views are considered as normal business objects
 +
* Entities are generated for database views
 +
* Database views can be queried using HQL and the DAL query APIs
 +
* Database views can be accessed through the [[XML REST Web Services|XML]] and [[JSON REST Web Services|JSON]] REST web service APIs
  
{{AvailableFrom|3.0PR17Q3}}
+
There is one difference between a database view and a database table: the DAL does not support updates on views; view business objects can be read and queried but not inserted or updated.
  
Starting from 3.0PR17Q3 columns allowing cross organization references allow users to select records from organizations not included in the tree of the record they are referenced from.
+
'''Note''': For the DAL to consider a view as a business object, it needs to have a primary key column defined in the Application Dictionary. This column does not need to be a real primary key in the database but it must hold unique values for each record of the view.
  
== DAL support for Database Views ==
+
== SQL Functions in HQL ==
 
 
From [[ERP/2.50/Release_Notes/2.50MP23|MP23]], the DAL supports views in practically the same way as normal tables defined in the application dictionary. This means that:
 
* database views are considered as normal business objects
 
* entities are generated for database views
 
* database views can be queried using HQL and the DAL query apis
 
* database views can be accessed through the [[XML_REST_Web_Services|XML]] and [[JSON_REST_Web_Services|JSON]] REST web service apis
 
 
 
There is one difference between a database view and a database table: the DAL does not support updates on views, view business objects can be read and queried but not inserted or updated.
 
  
Note that for the DAL to consider a view as a business object it needs to have a primary key column defined in the application dictionary. This column does not need to be a real primary key in the database but it must hold unique values for each record of the view.
+
To use SQL Functions in HQL, you first must make sure that Hibernate knows about your function. So in your Java code, you have to register the function. This is done by creating a class that implements the <code>SQLFunctionRegister</code> interface and annotated as <code>@ApplicationScoped</code>.
  
The changeset for supporting views is linked to [https://issues.openbravo.com/view.php?id=14636 this issue].
+
<code>
 
 
== SQL Functions in HQL ==
 
 
 
To use SQL Functions in HQL you first must make sure that Hibernate knows about your function. So in your java code you have to register the function. This is done by creating a class that implements the '''SQLFunctionRegister''' interface and annotated as ''@ApplicationScoped''.
 
<source lang="java">
 
 
@ApplicationScoped
 
@ApplicationScoped
 
public class ExampleSQLFunctionRegister implements SQLFunctionRegister {
 
public class ExampleSQLFunctionRegister implements SQLFunctionRegister {
  @Override
+
    @Override
  public Map<String, SQLFunction> getSQLFunctions() {
+
    public Map&lt;String, SQLFunction&gt; getSQLFunctions() {
    Map<String, SQLFunction> sqlFunctions = new HashMap<>();
+
        Map&lt;String, SQLFunction&gt; sqlFunctions = new HashMap&lt;&gt;();
    sqlFunctions.put("ad_column_identifier_std", new StandardSQLFunction("ad_column_identifier_std",
+
        sqlFunctions.put("ad_column_identifier_std", new StandardSQLFunction("ad_column_identifier_std", StandardBasicTypes.STRING));
        StandardBasicTypes.STRING));
+
        sqlFunctions.put("now", new StandardSQLFunction("now", StandardBasicTypes.DATE));
    sqlFunctions.put("now", new StandardSQLFunction("now", StandardBasicTypes.DATE));
+
        return sqlFunctions;
    return sqlFunctions;
+
    }
  }
 
 
}
 
}
</source>
+
</code>
  
The ''getSQLFunctions()'' method should be implemented providing a map with the SQL functions to be registered. This map will be retrieved during the DAL layer initialization in order to perform the registration automatically.
+
After registering the function, you can use it directly in an HQL query.
  
Please note that there are several SQL functions being already registered by default in core. See [https://code.openbravo.com/erp/devel/pi/file/tip/modules/org.openbravo.client.kernel/src/org/openbravo/client/kernel/KernelSQLFunctionRegister.java here] and [https://code.openbravo.com/erp/devel/pi/file/tip/modules/org.openbravo.advpaymentmngt/src/org/openbravo/advpaymentmngt/utility/APRMSQLFunctionRegister.java here].
+
== Executing Native SQL Queries ==
  
{{(!)| The SQLFunctionRegister interface is available starting from '''3.0PR18Q3''' version. See below to check how SQL functions were being registered in older releases.}}
+
The DAL also allows the execution of native SQL queries:
  
After registering the function you can use it directly in an HQL like this:
+
<code>
<source lang="java">final Session session = OBDal.getInstance().getSession();
+
String documentNo = (String) OBDal.getInstance().getSession()
final String qryStr = "select bc.id, ad_column_identifier_std('C_BP_Group', bc.id) from " + Category.ENTITY_NAME + " bc";
+
    .createNativeQuery("SELECT documentNo FROM c_order WHERE c_order_id = :id")
final Query qry = session.createQuery(qryStr);
+
    .setParameter("id", orderId)
</source>
+
    .uniqueResult();
 +
</code>
  
'''Before PR18Q3''': if you are still on an earlier version of PR18Q3, this code snippet shows how to register SQL functions in Hibernate:
+
'''Note''': The <code>createNativeQuery</code> method accepts a second argument where the returning result type may be specified. But this is '''not''' supported due to a bug in Hibernate.
<source lang="java">OBDal.getInstance().registerSQLFunction("ad_column_identifier_std",
 
        new StandardSQLFunction("ad_column_identifier_std", StandardBasicTypes.STRING));
 
</source>
 
  
'''Openbravo 2.50''': for Openbravo 2.50 the following workaround can be used to register functions:
+
== Runtime Model and the Dynamic API ==
<source lang="java">final Dialect dialect = ((SessionFactoryImpl) ((SessionImpl) OBDal.getInstance().getSession()).getSessionFactory()).getDialect();
 
dialect.getFunctions().put("get_uuid", new StandardSQLFunction("get_uuid", new StringType()));
 
</source>
 
You can do this once before executing the HQL in the Hibernate session.
 
  
== Runtime Model and the Dynamic API ==
+
The DAL makes the model (defined in the Application Dictionary) available at runtime. The runtime model is especially useful in generic functionality such as import/export, security and validation.
The DAL makes the model (defined in the Application Dictionary) available at runtime. The runtime model is especially useful in generic functionality such as import and export and security and validation.  
 
  
 
The runtime model consists of two main concepts:
 
The runtime model consists of two main concepts:
  
* [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/model/Entity.java Entity]: an entity models a database table and its associations to and from other tables (i.e. entities). An entity has an ''Entityname'' which is globally unique. In addition an entity has a runtime Java class which is used for the runtime Java representation. The entity class has methods to retrieve the full list of properties, the id properties or the identifier properties.
+
* '''Entity''': an entity models a database table and its associations to and from other tables. An entity has an ''EntityName'' which is globally unique.
* [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/model/Property.java Property]: a property corresponds to a column in the database. Some property specifics:
+
* '''Property''': a property corresponds to a column in the database. A property can be a primitive property (String, Date, numeric) or a reference to another entity.
** A property can be a primitive property (String, Date, numeric) or a reference to another entity (which can be retrieved through the ''getTargetEntity'' method).
 
** A property can be part of the primary key (''isId() == true'').
 
** A property can be part of the identifier of an entity (''isIdentifier() == true'').
 
  
The runtime model is available through the [https://code.openbravo.com/erp/devel/pi/file/ce3c0e6ad860/src/org/openbravo/base/model/ModelProvider.java ModelProvider] class (in ''org.openbravo.base.model'') which can be retrieved by calling ''ModelProvider.getInstance()''. The ModelProvider offers several methods to retrieve Entities from the in-memory model (the full list, by entity name or by Java class).  
+
The runtime model is available through the <code>ModelProvider</code> class (in <code>org.openbravo.base.model</code>) which can be retrieved by calling <code>ModelProvider.getInstance()</code>.
  
The runtime model makes it possible to use model-driven development techniques also at runtime. For example the runtime model together with the dynamic API offered by the BaseOBObject makes it possible to iterate through all properties (and their values) of a business object without knowing the exact type of the business objects.
+
The runtime model makes it possible to use model-driven development techniques also at runtime. For example, the runtime model together with the dynamic API offered by the BaseOBObject makes it possible to iterate through all properties (and their values) of a business object without knowing the exact type of the business objects.
 
 
The example below illustrates how to do this. This method below will translate any Openbravo ERP business entity into a simple XML document using the runtime model and the dynamic API:
 
 
 
<source lang="java">
 
  private void printXML(BaseOBObject bob) {
 
    // used to print a bit nicer xml
 
    final String indent = "\t ";
 
   
 
    // get the entity from the runtime model using the entity name of the object
 
    final String entityName = bob.getEntityName();
 
    final Entity e = ModelProvider.getInstance().getEntity(entityName);
 
    // Note: bob.getEntity() also gives the entity of the object
 
 
 
   
 
    // print the opening tag
 
    System.err.println("<" + e.getName() + ">");
 
   
 
    // iterate through the properties of the entity
 
    for (Property p : e.getProperties()) {
 
     
 
      // and get the value through the dynamic api offered by the BaseOBObject
 
      final Object value = bob.get(p.getName());
 
     
 
      // handle null, just create an empty tag for that
 
      if (value == null) {
 
        System.err.println(indent + "<" + p.getName() + "/>");
 
        continue;
 
      }
 
     
 
      // make a difference between a primitive and a reference type
 
      if (p.isPrimitive()) {
 
        // in reality some form of xml conversion/encoding should take place...
 
        System.err.println(indent + "<" + p.getName() + ">" + value + "</" + p.getName() + ">");
 
      } else {
 
        // cast to the parent of all openbravo objects
 
        final BaseOBObject referencedObject = (BaseOBObject) value;
 
        // assumes that the id is always a primitive type
 
        System.err.println(indent + "<" + p.getName() + ">" + referencedObject.getId() +
 
"</" + p.getName() + ">");
 
      }
 
    }
 
   
 
    // and the closing tag
 
    System.err.println("</" + e.getName() + ">");
 
  }
 
</source>
 
 
 
This example executes the following steps:
 
 
 
* Get the ''entityname'' from the object and retrieve the Entity from the runtime model through the ModelProvider instance.
 
* Create an opening tag and iterate through all properties of the entity (i.e. all columns of the table).
 
* For each property do the following:
 
*# Get the value for the property from the business object through the dynamic API.
 
*# Handle null values and check if the property is a primitive type or not.
 
*# For a primitive type just print the value.
 
*# For a reference type, cast the value to a BaseOBObject and print the id of the referenced object.
 
 
 
The above method will print the following output for a business partner group like the one stored in the hello world example above:
 
 
 
<source lang="xml">
 
<CoreBPGroup>
 
<deflt>true</deflt>
 
<description>hello world</description>
 
<name>hello world</name>
 
<value>hello world</value>
 
<updatedby>1000001</updatedby>
 
<updated>Sat Oct 04 12:31:57 CEST 2008</updated>
 
<createdby>1000001</createdby>
 
<created>Sat Oct 04 12:31:57 CEST 2008</created>
 
<active>true</active>
 
<org>1000000</org>
 
<client>1000000</client>
 
<id>ff8081811cc769b4011cc769e49b0002</id>
 
</CoreBPGroup>
 
</source>
 
 
 
This example gives a feel for how to use the runtime model.
 
 
 
In general when you are working with very generic code (cross-cutting concerns) which applies to all business objects then it can make sense to consider using the runtime model.
 
  
 
== Testing ==
 
== Testing ==
It is of vital importance to follow a test-driven development approach for every development project. This applies especially to backend process development (a little less for UI development which is more difficult to test automatically). The Data Access Layer is tested using many JUnit test cases. These can be found in the ''src-test'' folder in the Openbravo ERP project.
 
  
As a developer you can make use of the same test infrastructure as the Data Access Layer test cases. The only thing you need to do is to let your test class inherit from the [https://code.openbravo.com/erp/devel/pi/file/8051c8a30e72/src-test/org/openbravo/test/base/OBBaseTest.java OBBaseTest] class. The OBBaseTest class takes care of managing transactions, the context and initializing the DAL.  
+
It is of vital importance to follow a test-driven development approach for every development project. The Data Access Layer is tested using many JUnit test cases.
  
You can choose to run the test cases for specific users, see the methods in the OBBaseTest class for more information.
+
As a developer, you can make use of the same test infrastructure as the Data Access Layer test cases. The only thing you need to do is to let your test class inherit from the <code>OBBaseTest</code> class. The OBBaseTest class takes care of managing transactions, the context and initializing the DAL.
  
<source lang="java">
+
<code>
  public void testMyStuff() {
+
public void testMyStuff() {
  setTestUserContext();
+
    setTestUserContext();
  
 
     // do your test here
 
     // do your test here
  }
+
}
</source>
+
</code>
 
 
For more information on how-to develop test cases see this [[How_to_create_testcases | how-to]].
 
 
 
== Test your HQL: the HQL Query Tool ==
 
 
 
There is a Openbravo HQL Query Tool which allows you to try a HQL query directly in the Openbravo web interface. The module can be found in the central repository or in the forge [http://forge.openbravo.com/projects/hqlquerytool here]. The user manual of the HQL Query Tool can be found [http://forge.openbravo.com/plugins/mwiki/index.php/Hqlquerytool/User_Manual here].
 
  
 
== Calling Processes/Stored Procedures from the DAL ==
 
== Calling Processes/Stored Procedures from the DAL ==
Sometimes it makes sense to call a stored procedure from the DAL using the same db connection as it is being by the DAL. For this purpose the DAL includes two utility classes which make it easier to call processes and stored procedures through the DAL:
 
* [https://code.openbravo.com/erp/devel/pi/file/23ac74829856/src/org/openbravo/service/db/CallProcess.java CallProcess]
 
* [https://code.openbravo.com/erp/devel/pi/file/23ac74829856/src/org/openbravo/service/db/CallStoredProcedure.java Call Stored Procedure]
 
  
These classes make use of the same database connection as the DAL, in addition instead of working with String parameters you can work with the java (primitive) objects directly.
+
Sometimes, it makes sense to call a stored procedure from the DAL using the same database connection as it is being used by the DAL. For this purpose, the DAL includes two utility classes:
  
Both classes contain javadoc with a detailed description on how to use the class.
+
* '''[CallProcess]'''
 +
* '''[Call Stored Procedure]'''
  
Another interesting part when working with direct database updates (outside of Hibernate) is the following section in the trouble shooting guide: [[Common_Issues_Tips_and_Tricks#DAL_Queries_do_not_return_or_do_not_see_changes_in_the_database|changes not visible in the DAL after calling stored procedure]].
+
These classes make use of the same database connection as the DAL; in addition, instead of working with String parameters, you can work with the Java (primitive) objects directly.
  
 
== The DalConnectionProvider ==
 
== The DalConnectionProvider ==
  
To access and make use of classic Openbravo code it is often needed to have a ConnectionProvider object available. When combining DAL actions with classic Openbravo operations it makes sense to use one overall database connection and commit all actions in one step.
+
To access and make use of classic InfiniteERP code, it is often needed to have a ConnectionProvider object available. When combining DAL actions with classic InfiniteERP operations, it makes sense to use one overall database connection and commit all actions in one step.
  
To support this the DAL provides a special ConnectionProvider implementation which makes use of the DAL database connection: the [https://code.openbravo.com/erp/devel/pi/file/23ac74829856/src/org/openbravo/service/db/DalConnectionProvider.java DalConnectionProvider]. This class is simple to use, check out the javadoc for more information. The class can simply be used by instantiating it:
+
To support this, the DAL provides a special ConnectionProvider implementation which makes use of the DAL database connection: the <code>DalConnectionProvider</code>.
  
<source lang="java">
+
<code>
ConnectionProvider cp = DalConnectionProvider();  
+
ConnectionProvider cp = new DalConnectionProvider();
</source>
+
</code>
  
No additional information is needed for it to work properly.
+
For those queries that want to use the read-only pool, the connection provider must be defined as follows:
  
 +
<code>
 +
ConnectionProvider cp = DalConnectionProvider.getReadOnlyConnectionProvider();
 +
</code>
  
{{AvailableFrom|3.0PR17Q2}}
+
== Using the Data Access Layer in an Ant Task ==
  
For those queries that want to use the read-only pool, the connection provider must be defined as follows:
+
To ease the use of the DAL in Ant, a base Ant task class is offered by the DAL: the <code>DalInitializingTask</code> in the <code>org.openbravo.dal.core</code> package.
  
<source lang="java">
+
To make use of this class, the following changes need to be made to the Ant task and the custom Java Ant task implementation:
ConnectionProvider cp = DalConnectionProvider.getReadOnlyConnectionProvider();
 
</source>
 
  
== Using the Data Access Layer in an Ant task ==
+
* The custom Ant task Java class should inherit from the <code>DalInitializingTask</code>.
To ease the use of the DAL in Ant, a base Ant task class is offered by the DAL: the DalInitializingTask in the ''org.openbravo.dal.core'' package. This class takes care of initializing the DAL layer and other details (i.e. using the correct classloader).
+
* The custom Ant task Java class should implement a <code>doExecute</code> method instead of the <code>execute</code> method.
 +
* Two additional properties are required in the Ant task definition (in the <code>build.xml</code>):
 +
  * <code>propertiesFile="${base.config}/Openbravo.properties"</code>
 +
  * <code>userId="100"</code>
  
To make use of this class the following changes need to be made to the Ant task and the custom Java Ant task implementation:
+
== Important Information ==
  
* The custom Ant task Java class should inherit from the ''DalInitializatingTask''.
+
=== Hibernate Proxies ===
* The custom Ant task Java class should implement a ''doExecute'' method instead of the execute method in the custom Ant Java class (renaming the execute method to ''doExecute'' is enough).
 
* Two additional properties are required in the ant task definition (in the build.xml):
 
** propertiesFile="${base.config}/Openbravo.properties".
 
** userId="100".
 
  
The first property configures the location where the ''Openbravo.properties'' file can be found. The second property sets the user under which the task is performed.
+
To improve performance of single-ended associations, the DAL makes use of the Hibernate proxy functionality. The Hibernate proxy functionality wraps an object inside a Hibernate proxy object. This is done at runtime using CGLIB. The Hibernate proxy object takes care of loading the business object when it is actually accessed. The advantage of this approach is that if an object is never accessed, then it is neither loaded, saving performance.
 
 
== Some things to be aware of... ==
 
=== Hibernate Proxies ===
 
To improve performance of single-ended associations, the DAL makes use of the Hibernate proxy functionality. The Hibernate proxy functionality wraps an object inside a Hibernate proxy object. This is done at runtime using cglib. The Hibernate proxy object takes care of loading the business object when it is actually accessed. The advantage of this approach is that if an object is never accessed then it is neither loaded, saving performance.
 
  
 
However, the Hibernate proxy is very visible when a developer debugs through an application because the instance of an object at runtime will not be the exact class (for example BPGroup) but an instance of a Hibernate proxy class.
 
However, the Hibernate proxy is very visible when a developer debugs through an application because the instance of an object at runtime will not be the exact class (for example BPGroup) but an instance of a Hibernate proxy class.
  
To understand what the consequence of using Hibernate proxies it is essential that a developer using the DAL reads this part of the Hibernate manual: [http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/performance.html#performance-fetching-proxies].
+
=== Performance: Getting the ID of a BaseOBObject ===
  
=== Performance: getting the id of a BaseOBObject ===
+
In many cases, a developer just wants access to the ID or entity name of an object. To prevent loading of the business object when retrieving just this information, the <code>DalUtil</code> class in <code>org.openbravo.dal.core</code> offers an <code>getId</code> method and a <code>getEntityName</code> method. These methods work directly with the HibernateProxy object and do not load the underlying business object.
The previous section discussed the Hibernate proxy concept. A Hibernate proxy will load its wrapped business object when one of the methods on the business object is called. In many cases a developer just wants access to the id or entityname of an object. To prevent loading of the business object when retrieving just this information, the DalUtil class in ''org.openbravo.dal.core'' offers a ''getId'' method and a ''getEntityName'' method. These methods work directly with the HibernateProxy object and do not load the underlying business object.  
 
  
=== Hibernate inner workings ===
+
=== Creating a New Business Object with a Specific ID ===
To understand how Hibernate operates internally it is strongly encouraged to read chapter 21 of the Hibernate manual:
 
[http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/performance.html Improving Performance].
 
  
=== Classloading ===
+
Hibernate will detect that a business object is new when:
The DAL loads classes when initializing the DAL. The DAL as a default uses the context class loader of the thread. In some cases this does not work correctly (for example when using the DAL in Ant). The DAL uses the OBClassLoader class to make the classloader configurable. By calling ''OBClassLoader.setInstance'' with your own OBClassLoader you can control the class loader used by the DAL.
 
 
 
=== Creating a new business object with a specific id ===
 
  
Hibernate will detect that a business object is new when:
+
* The ID of the business object is not set
* the id of the business object is not set
+
* When the flag <code>newOBObject</code> is set to true explicitly
* when the flag newOBObject is set to true explicitly
 
  
So if you want to create a new business object with a specific id (by calling setId(...)) then you explicitly need to call businessObject.setNewOBObject(true). Otherwise, Hibernate will not detect the business object as being new and throw an exception ('count of batch update operation....').
+
So if you want to create a new business object with a specific ID (by calling <code>setId(...)</code>), then you explicitly need to call <code>businessObject.setNewOBObject(true)</code>. Otherwise, Hibernate will not detect the business object as being new and throw an exception.
  
== Coding Practices when using/extending the DAL ==
+
== Coding Practices when Using/Extending the DAL ==
This section discusses a number of essential coding practices which should be followed when using or extending the DAL.
 
  
 
=== Exception Structure ===
 
=== Exception Structure ===
All exceptions thrown by the DAL extend the base OBException class. The OBException is a ''RuntimeException'' so no explicit catch and throw statements are required.
 
  
The OBException class takes care of logging the exception in a correct way.  
+
All exceptions thrown by the DAL extend the base <code>OBException</code> class. The OBException is a <code>RuntimeException</code> so no explicit catch and throw statements are required.
  
When creating your own exception class it is best to extend OBException so that you can make use of the standard logging capabilities in the OBException (the logging capabilities will be extended over time).
+
When creating your own exception class, it is best to extend OBException so that you can make use of the standard logging capabilities.
  
=== Runtime Invariants: The Check class ===
+
=== Runtime Invariants: The Check Class ===
The Data Access Layer in various locations performs assertions or runtime invariant checks. For example to check if arguments are not null or that a certain condition is met. Implementing these type of checks helps to make your system much more robust. To make implementing these type of checks more convenient the Data Access Layer uses the Check class which is located in the ''org.openbravo.base.util'' package. The Check class offers methods to check for ''instanceof'', ''isNull'', ''isNotNull'', etc.
 
  
Using a common class for assertions throughout your code makes your code more readable and easier to understand (compared to implementing your own assertion checking).  
+
The Data Access Layer in various locations performs assertions or runtime invariant checks. To make implementing these type of checks more convenient, the DAL uses the <code>Check</code> class which is located in the <code>org.openbravo.base.util</code> package. The Check class offers methods to check for <code>instanceof</code>, <code>isNull</code>, <code>isNotNull</code>, etc.
  
 
=== Code Formatting ===
 
=== Code Formatting ===
The source code which is part of the Data Access Layer is formatted using one formatting template (in Eclipse). It is essential that when developing code in or using the Data Access Layer that this same code format template is used.
 
  
A common code format has the following benefits:
+
The source code, which is part of the Data Access Layer, is formatted using one formatting template. It is essential that when developing code in or using the Data Access Layer, that this same code format template is used. See [[Java Coding Conventions]] for more information.
  
* All Openbravo ERP code gets a uniform look and feel, looks cleaner/tidier and therefore look (and is) more professional.
+
== See Also ==
* The code will be easier to understand, less mistakes will be made.
 
* It is possible to do diff's on different versions of the code in mercurial (through your IDE), it is easier to understand changes over time.
 
* Solving mercurial conflicts is easier.
 
  
In Eclipse it is possible to automatically apply code formatting when saving a source file. This can be set in the ''Window > Preferences > Java > Editor > Save Actions''.
+
* [[How to work with the Data Access Layer]]
 +
* [[How to develop a DAL background process]]
 +
* [[How to do a complex query using the DAL-1]]
 +
* [[How to do a complex query using the DAL-2]]
 +
* [[How to call a stored procedure from the DAL]]
 +
* [[Java Coding Conventions]]
 +
* [[Application Dictionary]]
  
The code format template can be found in the config/eclipse folder in the Openbravo development project.
+
== References ==
  
== Tips & Tricks and Troubleshooting ==
+
* [https://docs.etendo.software/developer-guide/etendo-classic/concepts/data-access-layer/ Data Access Layer — Etendo Documentation]
  
For tips and tricks and common issues (and solutions) which you may encounter please visit the [[Common_Issues_Tips_and_Tricks#Data_Access_Layer|trouble shooting]] section.
+
----
  
 +
''This page is a derivative of [http://wiki.openbravo.com/wiki/Data_Access_Layer Data Access Layer] by [http://wiki.openbravo.com/wiki/Welcome_to_Openbravo Openbravo Wiki], used under [https://creativecommons.org/licenses/by-sa/2.5/es/ CC BY-SA 2.5 ES]. This work is licensed under [https://creativecommons.org/licenses/by-sa/2.5/ CC BY-SA 2.5].''
  
[[Category:Concepts]]
+
[[Category:Developers Guide]]
 +
[[Category:Documentation ERP]]

Latest revision as of 00:32, 21 September 2026

Back button.png   Back to Main Page


Data Access Layer

The goal of the InfiniteERP Data Access Layer (DAL) development is to strengthen the middle-tier in the application, i.e. to implement business logic in Java. The DAL provides the application developer with the following functionality:

  • Type-safe querying and retrieval of business objects from the database
  • A convenient API to update or create new data in the database
  • A type-safe interface to update information of a business object, increased productivity by making the properties of a business object directly visible through getters and setters (in the IDE)
  • Transaction and context handling
  • Security and validation checking
  • Automatically maps new entries in the Application Dictionary to database tables and columns
  • Generates Java class business objects (and their associations) on the basis of the Application Dictionary model

The DAL consists of a development-time and runtime part. The development-time part takes care of generating Java business object classes. The runtime part takes care of mapping Java classes to the database and supporting functionality such as security and validation.

A "Hello World" Example

As a first simple example, let's create a new business partner group and store it in the database:

// create the object through the factory final Category bpg = OBProvider.getInstance().get(Category.class);

// set some values bpg.setDefault(true); bpg.setDescription("hello world"); bpg.setName("hello world"); bpg.setValue("hello world"); bpg.setActive(true);

// store it in the database OBDal.getInstance().save(bpg);

There are a number of things which are important to note:

  • The above code does not set an explicit user context. The user context is set automatically when running the above code in InfiniteERP. However, in other environments, it has to be set explicitly — see User Context for more information.
  • There is a BPGroup class which models the data of the c_bp_group table. This class has type-safe getters and setters for all the data in this table.
  • A factory (the OBProvider) is used to create an instance of the BPGroup class.
  • The OBDal service is the main entry point into the Data Access Layer; it offers save, remove and query functionality.

The code snippet above also shows that you do not need to work with SQL or JDBC to work with the data from the database. As a developer, you work directly with objects and the available data is directly visible through the getters and setters.

As a next step, let's query for the business partner group and change its description:

// create an OBCriteria object and add a filter final OBCriteria<Category> obCriteria = OBDal.getInstance().createCriteria(Category.class); obCriteria.add(Restrictions.eq("name", "hello world"));

// perform the actual query returning a typed list final List<Category> categories = obCriteria.list(); final Category cat = categories.get(0);

// and set a new name cat.setName("another hello world"); OBDal.getInstance().save(cat);

This code snippet introduced a number of new concepts:

  • The OBDal service is used to create an OBCriteria object.
  • The OBCriteria object represents the query, implements the Hibernate Criteria interface and can be used as a standard Hibernate Criteria object. The OBCriteria object also supports sorting and paging parameters.
  • The OBCriteria list method performs the actual query; it returns a type-safe list of the requested objects.
  • After changing the name of the business partner group, you don't need to do an explicit save. At commit time, Hibernate will automatically detect dirty objects and save those.

DAL Architecture

The architecture for the Data Access Layer in InfiniteERP is implemented as follows:

  • Runtime model: the runtime model is the main driver for generating the business objects and the Hibernate mapping. It is also used extensively in security, export/import and in webservices implementations.
  • Hibernate Mapping: from the runtime model, the DAL (during initialization) generates a Hibernate mapping. This Hibernate mapping is used to initialize Hibernate.
  • Database Schema: the runtime model (actually the Application Dictionary) can be used to update the database schema. This is not available as part of the DAL but as part of the DBSourceManager product.
  • Data Access Layer: the DAL provides an API to store, query and remove business objects from the database.
  • Business Model/Logic Layer: the business model/logic layer contains the implementation of the business processes.
  • Business Services: the service layer exposes the business logic to the outside world.

The complete architecture runs inside of a context which provides security and transaction handling.

Business Object

This document uses the term business object to denote an entity and its dependent information. A business object can be a simple entity such as a currency which just has basic primitive fields. On the other hand, it can also be a structure of entities, for example an order header with its order line.

A business object structure always has one business object which is the owner of all the business objects in that structure. For example, for a sales order the sales order header business object is the owner of the complete structure (i.e., of the sales order lines). A sales order line cannot exist without its sales order header, and when a sales order header is removed, its sales order lines should also be removed.

The DAL uses the foreign keys from the child to parent to create the parent-child association in Java and Hibernate. More specifically: the foreign key columns which have the field isParent set to yes (checked/true) define the business object parent-child relations; other foreign key columns define standard many-to-one associations. For example, the foreign key field c_order_id in c_order_line is used to create the one-to-many association (in the in-memory runtime model) from c_order to c_order_line. This one-to-many association is then used to generate a List member in the Java Order class and a one-to-many mapping in Hibernate.

Note: Generated one-to-many properties in the parent entity load all children in memory when invoked, so they should only be generated when the expected amount of child records is low, otherwise it might cause an OutOfMemoryError.

DAL Main Interfaces

The DAL offers three main services to instantiate, create and query InfiniteERP business objects: OBDal, OBCriteria and OBProvider. The service classes can all be found in the org.openbravo.dal.service package.

OBDal

The OBDal instance (available through OBDal.getInstance()) is the main entrance point for retrieving and storing business objects in the database in a validated and secure way. It provides the following functions:

  • save: stores a new business object in the database or updates an existing business object. For existing business objects, it is not required to call this method as Hibernate does automatic dirty checking.
  • get: retrieves a single business object using its ID. There are two versions: one using the class name (of the generated business object) and one using the entity name.
  • remove: delete a business object from the database; the actual database delete is done at commit time.
  • create OBCriteria: OBCriteria objects are used for querying.
  • commitAndClose and rollbackAndClose: these methods can be used to implement custom transaction handling. Normally, this is done by the environment (InfiniteERP web container or InfiniteERP test).

The OBDal API makes extensive use of the OBCriteria and OBQuery classes to support querying.

The OBDal class allows accessing the read-only database (pool). In this case, the instance must be retrieved with the OBDal.getReadOnlyInstance() method.

OBCriteria

The OBCriteria class implements the Hibernate Criteria interface. It extends the standard Hibernate Criteria functionality for filtering on active, client and organization. In addition, it offers convenience methods to set order by and to perform count actions.

Summarizing, the OBCriteria object supports all Hibernate Criteria features:

  • Setting the where-clause of a filter using the Hibernate Criterion concept
  • Setting paging parameters such as first row and maximum number of results
  • Setting order by on the query
  • Specifying joins for performance reasons
  • Performing counts, average, etc.

An OBCriteria instance is created as follows:

final OBCriteria<Currency> obc = OBDal.getInstance().createCriteria(Currency.class);

Query on the name property:

obc.add(Restrictions.eq("name", "testname"));

Restrictions.eq is an instance of the Hibernate Criterion class. The Hibernate Criterion concept is an expression language coded in Java supporting most commonly used expressions (and, or, equal, not-equal, in, between, etc.).

Setting a descending order by on the name property:

obc.addOrderBy("name", false);

Or another one: order by the name property of a referenced business object:

obc.addOrderBy("product.name", false);

Set paging parameters, return 10 objects, beginning with the 100th:

obc.setFirstResult(100); obc.setMaxResults(10);

Also return inactive objects (as a default, only active objects are returned):

obc.setFilterOnActive(false);

Count the number of Currency objects in the database:

final int count = obc.count();

OBQuery

The OBQuery class is an extension of the Hibernate Query object. It extends the standard Hibernate Query functionality for filtering on active, client and organization.

The OBQuery object is created through the OBDal.createQuery method. The first argument of createQuery is a class or an entity name, the second argument is the where-clause.

final OBQuery<Category> obQuery = OBDal.getInstance().createQuery(Category.class, "name='testname' or searchKey='testvalue'"); final List<Category> bpgs = obQuery.list();

OBProvider

InfiniteERP business objects should not be instantiated directly using the new operator. Instead, the OBProvider class should be used to create an instance of the required business object. The OBProvider is located in the org.openbravo.base.provider package and can be retrieved using the method OBProvider.getInstance().

final Category bpg = OBProvider.getInstance().get(Category.class);

// The ENTITYNAME constant is created by the business object generation logic final BPGroup bpg = (BPGroup) OBProvider.getInstance().get(BPGroup.ENTITYNAME);

BaseOBObject

All InfiniteERP business objects inherit from the BaseOBObject class. This class is located in the org.openbravo.base.structure package.

The BaseOBObject class offers the following functionality:

  • Direct access (called the dynamic API) to all the properties and values of the data within the business object through the get(String propertyName) and set(String propertyName, Object value) methods. These methods are particularly useful when working with generic functions such as security and logging.
  • Access to the Entity describing the type and properties of the business object.
  • Security and validation checks when getting and setting values.
  • Access to the ID of the object through the getId method.
  • Access to the identifier of the object: the getIdentifier method of the BaseOBObject uses the identifier properties of the object to create a displayable title for that object.

Generated Business Object Classes

At development time, the DAL will generate business object classes for each table defined in the Application Dictionary. This is done as part of the compile.complete Ant task or can be done separately through the generate.entities Ant task. These generated classes are the classes which are normally used by a developer because they offer compile-time-checked typed access to properties.

The generated classes extend BaseOBObject and offer typed wrapper getters and setters around the generic set and get method of the parent BaseOBObject class:

public String getName() {

   return (String) get("name");

}

public void setName(String name) {

   set("name", name);

}

In addition, the generated Java classes set default values (in the constructor) and have a static ENTITYNAME variable which must be used when it is required to refer directly to an entity name of an entity.

Entity, Property and Column Naming

The Data Access Layer uses names defined in the Application Dictionary for different purposes:

  • XML tag names in REST web services and import/export
  • Class names of generated Entity classes
  • Member names of members of generated Entity classes
  • To detect that a certain Entity implements/supports a certain interface

The Application Dictionary historically allows many different types of names (also ones that are illegal for XML/Java). Therefore, the data access layer applies specific conversion logic to always ensure that names are allowed and unique for XML/Java.

Entity Naming

An entity (corresponds to a table in InfiniteERP) has different names, relevant for different situations:

  • A table name (stored in AD_Table.tablename) which is the database table name in the physical database.
  • An entity name (present in AD_Table.name) which is a globally unique name. It corresponds to the XML tag name used for that entity.
  • A Java class name (stored in AD_Table.classname), the class name is used when generating the Java business object. It is unique within the data package of the table.

Each table (i.e., entity) in InfiniteERP belongs to a data package. A data package has a Java package field which defines the Java package in which the entity Java class is generated.

Warning: AD_Table.name should not contain blank spaces. If the name of the table contains spaces, the process of building the entity name will remove those spaces (e.g., My Table will be converted to MyTable).

Property Naming

For property naming, the logic has two distinct steps:

1. First determine an initial property name, and 2. Correct/convert this property name.

The initial property name is determined as follows:

  • For standard primitive type (varchar, numeric, etc.) and foreign key columns: the value in AD_Column.name is used as the start of the property name calculation.
  • For properties which model a list of child entities (one-to-many): if the column name on the other side (from the child to the parent) is the same as the primary key column name of the parent, then use the entity name of the child plus the suffix "List" (e.g., OrderLineList in the Order entity).

Next, the property name generation performs the following steps:

  • Spaces are removed and used to camelcase (e.g., "Enable in Cash" → EnableInCash).
  • Underscores are removed and used to camelcase (e.g., "C_Poc_email_ID" → CPocEmailID).
  • "Illegal" characters are removed: only characters from a to z and A to Z and numbers (not as prefix) are maintained.
  • The first character is lowercased (e.g., AccountingFactaccountingFact).

Important Interfaces

The generated classes implement a set of interfaces which can be used to check if a certain instance of a class has specific functionality available:

  • [ClientEnabled]: flags an object as having a getClient/setClient method and as an object which is stored by Client. InfiniteERP automatically detects that a table implements this interface if it has a column with the name client.
  • [OrganizationEnabled]: an object implementing this interface has getOrganization/setOrganization methods. InfiniteERP automatically detects this if it has a column with the name organization.
  • [ActiveEnabled]: an object implementing this interface has an active flag (boolean) which can be reached through the isActive/setActive methods. InfiniteERP automatically detects this if it has a column with the name active.
  • [Traceable]: a Traceable object has audit information: created and updated (date fields), and createdBy and updatedBy (contain a User). InfiniteERP automatically detects this if it has the following columns (all are required): creationDate, created, updated, updatedBy.

Client, Organization and Audit Information

The above interfaces are used by the DAL when an object is saved for the first time or updated in the database:

  • A ClientEnabled object for which the Client is not set will get the current Client of the user (present in the user context).
  • An OrganizationEnabled object for which the Organization is not set will get the current Organization of the user (present in the user context).
  • A Traceable object: when saved for the first time, the created, createdBy, updated and updatedBy are set. When an object is updated, then the updated and updatedBy are set.

So, a developer does not need to explicitly set this information in a new or existing object.

Creating a New Instance of a Business Object

A business object may never be created using the Java new operator. All business objects should be created using the OBProvider factory class:

final Category bpg = OBProvider.getInstance().get(Category.class);

Hibernate will detect that a business object is new when:

  • The ID of the business object is not set
  • When the flag newOBObject is set to true explicitly

So, if you want to create a new business object with a specific ID (by calling setId(...)), then you explicitly need to call businessObject.setNewOBObject(true). Otherwise, Hibernate will throw an exception.

User Context

The DAL operates within a user context. The user context is implemented in the OBContext class in the org.openbravo.dal.core package. The OBContext is initialized using a userId. On the basis of this userId, the OBContext computes the role, clients, organizations and accessible entities. This information is used by the DAL for security checking and automatic filtering on client and organization.

The OBContext is stored as a ThreadLocal variable and the DAL always assumes that there is one available.

User Context in a Running InfiniteERP Instance

When the code runs inside an InfiniteERP application, the user context is always set. This is done through a request filter (the DalRequestFilter in the org.openbravo.dal.core package).

User Context in a Standalone Situation

The user context can also be set by calling OBContext.setOBContext(userId) with a userId which exists in the ad_user table.

Administrator Mode

The OBContext provides an Administrator Mode which can be used to perform administrative actions even if the user does not have enough privileges. This mode bypasses the Entity Access checking and it does not filter by Client or Organization.

An additional Administrator Mode is provided, which bypasses the Entity Access checking but does filter by Client Organization.

The syntax to activate the restricted Admin Mode:

try {

   OBContext.setAdminMode(true);
   // do administrative things here

} finally {

   OBContext.restorePreviousMode();

}

In some cases, it is necessary to prevent also client/organization check; this can be done using OBContext.setAdminMode(false).

Note: The calls to setAdminMode/restorePreviousMode are balanced, meaning that for each call to setAdminMode there is also exactly one call to restorePreviousMode.

Transaction and Session

The DAL implements the so-called open-session-view pattern. With a slight variation: the DAL will automatically create a Hibernate Session and start a transaction when the first data access takes place (if none existed). So not when the HTTP request begins. The Session and Transaction are placed in a ThreadLocal and re-used for subsequent data access actions in the same thread.

An important thing to be aware of: normally all database actions are flushed to the database when the session is committed. In a web environment, this is at the end of the HTTP request. To perform flush on demand, call the OBDal.getInstance().flush() method.

  • Within InfiniteERP: the open-session-view pattern is used. When running the code in the InfiniteERP application, the transaction commit and session close take place at the end of the HTTP request. If an exception occurs, then a rollback is performed.
  • In test environment: the DAL base test class takes care of committing or rolling back transactions.
  • Standalone: if the code is running standalone, then an explicit commit or rollback needs to be performed. This can be done through the OBDal methods: OBDal.getInstance().commitAndClose() or OBDal.getInstance().rollbackAndClose().

Warning: SQLC and DAL: The standard InfiniteERP database access (through Windows) works outside of the DAL. This means that database access uses a different connection than the DAL. If both connections update the database, then it is possible that a deadlock situation happens. So, when working/updating through both the DAL and SQLC, one should always first commit/close the connection of the DAL explicitly before continuing with SQLC, or the other way around.

Security and Validation

The DAL performs many different security and validation checks, in both read and write mode. A security violation results in a SecurityException, a validation error in a ValidationException.

Write Access

Write access checks are done when the OBDal save or remove methods are called or when a business object is saved by Hibernate (at flush/commit). For write access, the following checks are performed:

  • The user must have access to a window/tab which displays the entity. See the AD_Window_Access table.
  • The organization of the business object must be in the list of writable organizations of that user.
  • The client of the business object must be in the list of writable clients of that user.

If any of the above checks fails, then a SecurityException is thrown.

Read Access

A user can only view information from their own clients and accessible organizations. This is ensured by the DAL by automatically adding filter criteria in the OBCriteria object.

Read access is checked for both direct read access and derived read access:

  • Direct read access: allows a user to see all the information of a certain entity. Based on the window access tables.
  • Derived read access: only the active property, audit info and the ID and identifier may be read by a user. For example, if a user may directly read an invoice entity, and an invoice refers to a currency, then the user has derived read access to a currency.

Delete Check

A user may delete a business object if:

  • They have write access to the entity of the business object.
  • The entity is not set as not-deletable, this is defined in the AD_Table.

Validation

Property values are validated when the setter is called or the generic set method on the BaseOBObject is called. The following checks are performed:

  • A check is done if the instance of the value is valid for that property
  • For String values, a check is done on length or on the allowed String values (list/enumerate values)
  • For numeric values, the min and max are checked
  • For mandatory values, a check is done if the value is unequal to null

Entity Organization Validation

A business object may only refer to other business objects which belong to the natural tree of the organization of the business object. This validation is done when a business object is saved (i.e., when the session commits/flushes).

Cross Organization References: Entity Organization validation can be bypassed if the foreign key column that references to the other object is marked as Allow Cross Organization Reference and the context is in Cross Organization Reference Administrator Mode.

DAL Support for Database Views

The DAL supports views in practically the same way as normal tables defined in the Application Dictionary. This means that:

  • Database views are considered as normal business objects
  • Entities are generated for database views
  • Database views can be queried using HQL and the DAL query APIs
  • Database views can be accessed through the XML and JSON REST web service APIs

There is one difference between a database view and a database table: the DAL does not support updates on views; view business objects can be read and queried but not inserted or updated.

Note: For the DAL to consider a view as a business object, it needs to have a primary key column defined in the Application Dictionary. This column does not need to be a real primary key in the database but it must hold unique values for each record of the view.

SQL Functions in HQL

To use SQL Functions in HQL, you first must make sure that Hibernate knows about your function. So in your Java code, you have to register the function. This is done by creating a class that implements the SQLFunctionRegister interface and annotated as @ApplicationScoped.

@ApplicationScoped public class ExampleSQLFunctionRegister implements SQLFunctionRegister {

   @Override
   public Map<String, SQLFunction> getSQLFunctions() {
       Map<String, SQLFunction> sqlFunctions = new HashMap<>();
       sqlFunctions.put("ad_column_identifier_std", new StandardSQLFunction("ad_column_identifier_std", StandardBasicTypes.STRING));
       sqlFunctions.put("now", new StandardSQLFunction("now", StandardBasicTypes.DATE));
       return sqlFunctions;
   }

}

After registering the function, you can use it directly in an HQL query.

Executing Native SQL Queries

The DAL also allows the execution of native SQL queries:

String documentNo = (String) OBDal.getInstance().getSession()

   .createNativeQuery("SELECT documentNo FROM c_order WHERE c_order_id = :id")
   .setParameter("id", orderId)
   .uniqueResult();

Note: The createNativeQuery method accepts a second argument where the returning result type may be specified. But this is not supported due to a bug in Hibernate.

Runtime Model and the Dynamic API

The DAL makes the model (defined in the Application Dictionary) available at runtime. The runtime model is especially useful in generic functionality such as import/export, security and validation.

The runtime model consists of two main concepts:

  • Entity: an entity models a database table and its associations to and from other tables. An entity has an EntityName which is globally unique.
  • Property: a property corresponds to a column in the database. A property can be a primitive property (String, Date, numeric) or a reference to another entity.

The runtime model is available through the ModelProvider class (in org.openbravo.base.model) which can be retrieved by calling ModelProvider.getInstance().

The runtime model makes it possible to use model-driven development techniques also at runtime. For example, the runtime model together with the dynamic API offered by the BaseOBObject makes it possible to iterate through all properties (and their values) of a business object without knowing the exact type of the business objects.

Testing

It is of vital importance to follow a test-driven development approach for every development project. The Data Access Layer is tested using many JUnit test cases.

As a developer, you can make use of the same test infrastructure as the Data Access Layer test cases. The only thing you need to do is to let your test class inherit from the OBBaseTest class. The OBBaseTest class takes care of managing transactions, the context and initializing the DAL.

public void testMyStuff() {

   setTestUserContext();
   // do your test here

}

Calling Processes/Stored Procedures from the DAL

Sometimes, it makes sense to call a stored procedure from the DAL using the same database connection as it is being used by the DAL. For this purpose, the DAL includes two utility classes:

  • [CallProcess]
  • [Call Stored Procedure]

These classes make use of the same database connection as the DAL; in addition, instead of working with String parameters, you can work with the Java (primitive) objects directly.

The DalConnectionProvider

To access and make use of classic InfiniteERP code, it is often needed to have a ConnectionProvider object available. When combining DAL actions with classic InfiniteERP operations, it makes sense to use one overall database connection and commit all actions in one step.

To support this, the DAL provides a special ConnectionProvider implementation which makes use of the DAL database connection: the DalConnectionProvider.

ConnectionProvider cp = new DalConnectionProvider();

For those queries that want to use the read-only pool, the connection provider must be defined as follows:

ConnectionProvider cp = DalConnectionProvider.getReadOnlyConnectionProvider();

Using the Data Access Layer in an Ant Task

To ease the use of the DAL in Ant, a base Ant task class is offered by the DAL: the DalInitializingTask in the org.openbravo.dal.core package.

To make use of this class, the following changes need to be made to the Ant task and the custom Java Ant task implementation:

  • The custom Ant task Java class should inherit from the DalInitializingTask.
  • The custom Ant task Java class should implement a doExecute method instead of the execute method.
  • Two additional properties are required in the Ant task definition (in the build.xml):
 * propertiesFile="${base.config}/Openbravo.properties"
 * userId="100"

Important Information

Hibernate Proxies

To improve performance of single-ended associations, the DAL makes use of the Hibernate proxy functionality. The Hibernate proxy functionality wraps an object inside a Hibernate proxy object. This is done at runtime using CGLIB. The Hibernate proxy object takes care of loading the business object when it is actually accessed. The advantage of this approach is that if an object is never accessed, then it is neither loaded, saving performance.

However, the Hibernate proxy is very visible when a developer debugs through an application because the instance of an object at runtime will not be the exact class (for example BPGroup) but an instance of a Hibernate proxy class.

Performance: Getting the ID of a BaseOBObject

In many cases, a developer just wants access to the ID or entity name of an object. To prevent loading of the business object when retrieving just this information, the DalUtil class in org.openbravo.dal.core offers an getId method and a getEntityName method. These methods work directly with the HibernateProxy object and do not load the underlying business object.

Creating a New Business Object with a Specific ID

Hibernate will detect that a business object is new when:

  • The ID of the business object is not set
  • When the flag newOBObject is set to true explicitly

So if you want to create a new business object with a specific ID (by calling setId(...)), then you explicitly need to call businessObject.setNewOBObject(true). Otherwise, Hibernate will not detect the business object as being new and throw an exception.

Coding Practices when Using/Extending the DAL

Exception Structure

All exceptions thrown by the DAL extend the base OBException class. The OBException is a RuntimeException so no explicit catch and throw statements are required.

When creating your own exception class, it is best to extend OBException so that you can make use of the standard logging capabilities.

Runtime Invariants: The Check Class

The Data Access Layer in various locations performs assertions or runtime invariant checks. To make implementing these type of checks more convenient, the DAL uses the Check class which is located in the org.openbravo.base.util package. The Check class offers methods to check for instanceof, isNull, isNotNull, etc.

Code Formatting

The source code, which is part of the Data Access Layer, is formatted using one formatting template. It is essential that when developing code in or using the Data Access Layer, that this same code format template is used. See Java Coding Conventions for more information.

See Also

References


This page is a derivative of Data Access Layer by Openbravo Wiki, used under CC BY-SA 2.5 ES. This work is licensed under CC BY-SA 2.5.