fixed issue 24104: unnecessary evaluation of computed columns
authorAsier Lostalé <asier.lostale@openbravo.com>
Fri, 19 Jul 2013 09:39:21 +0200
changeset 20857 7d6d644b9b9e
parent 20856 d1cfd2567d69
child 20964 0b8c20d9ec64
fixed issue 24104: unnecessary evaluation of computed columns

Computed columns are no longer directly evaluated when retriving DAL objects
they are now lazily calculated through a proxy.
modules/org.openbravo.service.datasource/src/org/openbravo/service/datasource/DefaultDataSourceService.java
modules/org.openbravo.service.json/src/org/openbravo/service/json/AdvancedQueryBuilder.java
modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonToDataConverter.java
src-test/org/openbravo/test/AllAntTaskTests.java
src-test/org/openbravo/test/AllTests.java
src-test/org/openbravo/test/AntTaskTests.java
src-test/org/openbravo/test/dal/ComputedColumnsTest.java
src-test/org/openbravo/test/model/RuntimeModelTest.java
src/org/openbravo/base/gen/GenerateEntitiesTask.java
src/org/openbravo/base/gen/entity.ftl
src/org/openbravo/base/gen/entityComputedColumns.ftl
src/org/openbravo/base/model/Entity.java
src/org/openbravo/base/model/ModelProvider.java
src/org/openbravo/base/model/Property.java
src/org/openbravo/dal/core/DalMappingGenerator.java
src/org/openbravo/dal/core/template.hbm.xml
src/org/openbravo/dal/core/template_main.hbm.xml
src/org/openbravo/dal/xml/EntityXMLConverter.java
src/org/openbravo/dal/xml/StaxXMLEntityConverter.java
src/org/openbravo/dal/xml/XMLEntityConverter.java
src/org/openbravo/service/db/DataImportService.java
src/org/openbravo/service/rest/DalWebService.java
src/org/openbravo/service/system/SystemService.java
--- a/modules/org.openbravo.service.datasource/src/org/openbravo/service/datasource/DefaultDataSourceService.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/modules/org.openbravo.service.datasource/src/org/openbravo/service/datasource/DefaultDataSourceService.java	Fri Jul 19 09:39:21 2013 +0200
@@ -328,7 +328,7 @@
 
     final List<DataSourceProperty> result = new ArrayList<DataSourceProperty>();
     for (Property prop : entity.getProperties()) {
-      if (prop.isOneToMany()) {
+      if (prop.isOneToMany() || prop.isProxy()) {
         continue;
       }
 
--- a/modules/org.openbravo.service.json/src/org/openbravo/service/json/AdvancedQueryBuilder.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/modules/org.openbravo.service.json/src/org/openbravo/service/json/AdvancedQueryBuilder.java	Fri Jul 19 09:39:21 2013 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2009-2012 Openbravo SLU 
+ * All portions are Copyright (C) 2009-2013 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -476,6 +476,12 @@
     // or uses the display column to display that in the grid
     Property useProperty = property;
     String useFieldName = fieldName.replace(DalUtil.FIELDSEPARATOR, DalUtil.DOT);
+
+    if (useProperty.isComputedColumn()) {
+      // Computed columns are not directly accessed but through _computedColumns proxy
+      useFieldName = Entity.COMPUTED_COLUMNS_PROXY_PROPERTY + DalUtil.DOT + useFieldName;
+    }
+
     if (properties.size() >= 2) {
       final Property refProperty = properties.get(properties.size() - 2);
       if (refProperty.getDomainType() instanceof TableDomainType) {
@@ -1195,6 +1201,13 @@
         }
       }
     } else {
+      Entity searchEntity = getEntity();
+      Property property = searchEntity.getProperty(localOrderBy, false);
+      if (property != null && property.isComputedColumn()) {
+        // Computed columns are accessed through proxy
+        localOrderBy = Entity.COMPUTED_COLUMNS_PROXY_PROPERTY + DalUtil.DOT + localOrderBy;
+      }
+
       paths.add(localOrderBy);
     }
 
--- a/modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonToDataConverter.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonToDataConverter.java	Fri Jul 19 09:39:21 2013 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2009-2011 Openbravo SLU 
+ * All portions are Copyright (C) 2009-2013 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -766,6 +766,7 @@
     // do not change not updatable properties
     // Updatable is a UI concept
     // doNotHandleThisProperty |= !obObject.isNewOBObject() && !property.isUpdatable();
+    doNotHandleThisProperty |= property.isProxy();
     return doNotHandleThisProperty;
   }
 
--- a/src-test/org/openbravo/test/AllAntTaskTests.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src-test/org/openbravo/test/AllAntTaskTests.java	Fri Jul 19 09:39:21 2013 +0200
@@ -24,6 +24,7 @@
 
 import org.openbravo.erpCommon.info.ClassicSelectorTest;
 import org.openbravo.test.dal.AdminContextTest;
+import org.openbravo.test.dal.ComputedColumnsTest;
 import org.openbravo.test.dal.DalComplexQueryRequisitionTest;
 import org.openbravo.test.dal.DalComplexQueryTestOrderLine;
 import org.openbravo.test.dal.DalConnectionProviderTest;
@@ -104,6 +105,7 @@
     suite.addTestSuite(ReadByNameTest.class);
     suite.addTestSuite(AdminContextTest.class);
     suite.addTestSuite(ViewTest.class);
+    suite.addTestSuite(ComputedColumnsTest.class);
 
     // expression
     suite.addTestSuite(EvaluationTest.class);
--- a/src-test/org/openbravo/test/AllTests.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src-test/org/openbravo/test/AllTests.java	Fri Jul 19 09:39:21 2013 +0200
@@ -23,6 +23,7 @@
 import junit.framework.TestSuite;
 
 import org.openbravo.erpCommon.info.ClassicSelectorTest;
+import org.openbravo.test.dal.ComputedColumnsTest;
 import org.openbravo.test.dal.DalConnectionProviderTest;
 import org.openbravo.test.dal.DalCopyTest;
 import org.openbravo.test.dal.DalFilterTest;
@@ -80,6 +81,7 @@
     suite.addTestSuite(DalStoredProcedureTest.class);
     suite.addTestSuite(ReadByNameTest.class);
     suite.addTestSuite(DalPerformanceProxyTest.class);
+    suite.addTestSuite(ComputedColumnsTest.class);
 
     // model
     suite.addTestSuite(RuntimeModelTest.class);
--- a/src-test/org/openbravo/test/AntTaskTests.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src-test/org/openbravo/test/AntTaskTests.java	Fri Jul 19 09:39:21 2013 +0200
@@ -23,6 +23,7 @@
 import junit.framework.TestSuite;
 
 import org.openbravo.erpCommon.info.ClassicSelectorTest;
+import org.openbravo.test.dal.ComputedColumnsTest;
 import org.openbravo.test.dal.DalComplexQueryRequisitionTest;
 import org.openbravo.test.dal.DalComplexQueryTestOrderLine;
 import org.openbravo.test.dal.DalConnectionProviderTest;
@@ -102,6 +103,7 @@
     suite.addTestSuite(OBContextTest.class);
     suite.addTestSuite(DalStoredProcedureTest.class);
     suite.addTestSuite(ReadByNameTest.class);
+    suite.addTestSuite(ComputedColumnsTest.class);
 
     // expression
     suite.addTestSuite(EvaluationTest.class);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/org/openbravo/test/dal/ComputedColumnsTest.java	Fri Jul 19 09:39:21 2013 +0200
@@ -0,0 +1,139 @@
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License.
+ * The Original Code is Openbravo ERP.
+ * The Initial Developer of the Original Code is Openbravo SLU
+ * All portions are Copyright (C) 2013 Openbravo SLU
+ * All Rights Reserved.
+ * Contributor(s):  ______________________________________.
+ ************************************************************************
+ */
+
+package org.openbravo.test.dal;
+
+import java.util.Set;
+
+import org.hibernate.QueryException;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.engine.EntityKey;
+import org.hibernate.stat.SessionStatistics;
+import org.openbravo.dal.core.DalUtil;
+import org.openbravo.dal.core.SessionHandler;
+import org.openbravo.dal.service.OBCriteria;
+import org.openbravo.dal.service.OBDal;
+import org.openbravo.dal.service.OBQuery;
+import org.openbravo.model.common.order.Order;
+import org.openbravo.model.common.order.Order_ComputedColumns;
+import org.openbravo.test.base.BaseTest;
+
+/**
+ * Set of tests for computed columns lazy loading
+ * 
+ * @author alostale
+ * 
+ */
+public class ComputedColumnsTest extends BaseTest {
+  private static final String EXCEPTION_MSG = "could not resolve property: deliveryStatus of: Order";
+
+  /**
+   * Tests computed columns are lazily loaded
+   */
+  public void testLazyLoad() {
+    setTestUserContext();
+
+    // load one order
+    OBCriteria<Order> qOrder = OBDal.getInstance().createCriteria(Order.class);
+    qOrder.setMaxResults(1);
+    Order order = qOrder.list().get(0);
+
+    // check it is in memory but computed columns are not already loaded
+    assertTrue("DAL Order loaded",
+        dalObjectLoaded(Order.ENTITY_NAME, (String) DalUtil.getId(order)));
+    assertFalse("DAL Order computed columns shouldn't be loaded",
+        dalObjectLoaded(Order_ComputedColumns.ENTITY_NAME, (String) DalUtil.getId(order)));
+
+    // load computed columns
+    order.getDeliveryStatus();
+
+    // check they are now loaded in memory
+    assertTrue("DAL Order computed columns should be loaded",
+        dalObjectLoaded(Order_ComputedColumns.ENTITY_NAME, (String) DalUtil.getId(order)));
+  }
+
+  /**
+   * Test it is possible to filter in HQL by computed columns accessing them through the proxy.
+   * <p>
+   * Note this way of filtering is potentially harmful in terms of performance because computed
+   * column need to be calculated in order to do the filtering.
+   */
+  public void testComputedColumnHQLFilter() {
+    setTestUserContext();
+
+    // filtering by computed column through proxy
+    OBQuery<Order> qOrder = OBDal.getInstance().createQuery(Order.class,
+        "as o where o._computedColumns.deliveryStatus = 100");
+
+    qOrder.count();
+  }
+
+  /**
+   * Tests computed columns can not be used in OBCriteria
+   */
+  public void testComputedColumnCriteriaFilter() {
+    setTestUserContext();
+
+    // try to filter in Criteria by computed column...
+    OBCriteria<Order> qOrder = OBDal.getInstance().createCriteria(Order.class);
+    qOrder.add(Restrictions.eq(Order.COMPUTED_COLUMN_DELIVERYSTATUS, 100));
+    boolean thrown = false;
+    try {
+      qOrder.count();
+    } catch (QueryException e) {
+      thrown = e.getMessage().startsWith(EXCEPTION_MSG);
+    }
+
+    // ... it shouldn't be possible
+    assertTrue("Computed columns sholdn't be usable in OBCriteria", thrown);
+  }
+
+  /**
+   * Direct access to computed columns in HQL was allowed prior to MP27, now it is not anymore and
+   * proxy needs to be used.
+   */
+  public void testComputedColumnHQLFilterOldWay() {
+    setTestUserContext();
+
+    // try to filter in HQL directly by computed column...
+    OBQuery<Order> qOrder = OBDal.getInstance().createQuery(Order.class,
+        "as o where o.deliveryStatus = 100");
+
+    boolean thrown = false;
+    try {
+      qOrder.count();
+    } catch (QueryException e) {
+      thrown = e.getMessage().startsWith(EXCEPTION_MSG);
+    }
+
+    // ... it shouldn't be possible, proxy should be used to reach it
+    assertTrue("Computed columns can't be directly used in HQL", thrown);
+  }
+
+  @SuppressWarnings("unchecked")
+  private boolean dalObjectLoaded(String entityName, String id) {
+    SessionStatistics stats = SessionHandler.getInstance().getSession().getStatistics();
+    for (EntityKey k : (Set<EntityKey>) stats.getEntityKeys()) {
+      if (entityName.equals(k.getEntityName()) && id.equals(k.getIdentifier())) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
--- a/src-test/org/openbravo/test/model/RuntimeModelTest.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src-test/org/openbravo/test/model/RuntimeModelTest.java	Fri Jul 19 09:39:21 2013 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2008-2011 Openbravo SLU 
+ * All portions are Copyright (C) 2008-2013 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -214,7 +214,8 @@
   public void testColumnIdSet() {
     for (Entity entity : ModelProvider.getInstance().getModel()) {
       for (Property property : entity.getProperties()) {
-        if (property.isOneToMany() || property.isOneToOne() || property.isCompositeId()) {
+        if (property.isOneToMany() || property.isOneToOne() || property.isCompositeId()
+            || Entity.COMPUTED_COLUMNS_PROXY_PROPERTY.equals(property.getName())) {
           continue;
         }
         assertNotNull("Property " + property + " does not have a columnid ", property.getColumnId());
--- a/src/org/openbravo/base/gen/GenerateEntitiesTask.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/base/gen/GenerateEntitiesTask.java	Fri Jul 19 09:39:21 2013 +0200
@@ -36,6 +36,7 @@
 import org.openbravo.base.exception.OBException;
 import org.openbravo.base.model.Entity;
 import org.openbravo.base.model.ModelProvider;
+import org.openbravo.base.model.Property;
 import org.openbravo.base.session.OBPropertiesProvider;
 
 import freemarker.template.Configuration;
@@ -124,17 +125,30 @@
     File ftlFile = new File(getBasePath(), ftlFilename);
     freemarker.template.Template template = createTemplateImplementation(ftlFile);
 
+    // template for computed columns entities
+    String ftlComputedFilename = "org/openbravo/base/gen/entityComputedColumns.ftl";
+    File ftlComputedFile = new File(getBasePath(), ftlComputedFilename);
+    freemarker.template.Template templateComputed = createTemplateImplementation(ftlComputedFile);
+
     // process template & write file for each entity
     List<Entity> entities = ModelProvider.getInstance().getModel();
     for (Entity entity : entities) {
       // If the entity is associated with a datasource based table, do not generate a Java file
-      if (!entity.isDataSourceBased()) {
-        String classfileName = entity.getClassName().replaceAll("\\.", "/") + ".java";
+      if (entity.isDataSourceBased()) {
+        continue;
+      }
+
+      File outFile;
+      String classfileName;
+      Writer outWriter = null;
+
+      if (!entity.isVirtualEntity()) {
+        classfileName = entity.getClassName().replaceAll("\\.", "/") + ".java";
         log.debug("Generating file: " + classfileName);
-        File outFile = new File(srcGenPath, classfileName);
+        outFile = new File(srcGenPath, classfileName);
         new File(outFile.getParent()).mkdirs();
 
-        Writer outWriter;
+        outWriter = null;
         try {
           outWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),
               "UTF-8"));
@@ -143,6 +157,49 @@
           processTemplate(template, data, outWriter);
         } catch (IOException e) {
           log.error("Error generating file: " + classfileName, e);
+        } finally {
+          if (outWriter != null) {
+            try {
+              outWriter.close();
+            } catch (IOException ignore) {
+            }
+          }
+        }
+      }
+
+      if (entity.hasComputedColumns()) {
+        classfileName = entity.getPackageName().replaceAll("\\.", "/") + "/"
+            + entity.getSimpleClassName() + Entity.COMPUTED_COLUMNS_CLASS_APPENDIX + ".java";
+        log.debug("Generating file: " + classfileName);
+        outFile = new File(srcGenPath, classfileName);
+        new File(outFile.getParent()).mkdirs();
+        outWriter = null;
+        try {
+          outWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),
+              "UTF-8"));
+          Map<String, Object> data = new HashMap<String, Object>();
+          data.put("entity", entity);
+
+          List<Property> properties = entity.getComputedColumnProperties();
+
+          properties.add(entity.getProperty("client"));
+          properties.add(entity.getProperty("organization"));
+
+          data.put("properties", properties);
+          List<String> imports = entity.getJavaImports(properties);
+          imports.remove("import org.openbravo.base.structure.ActiveEnabled;");
+          imports.remove("import org.openbravo.base.structure.Traceable;");
+          data.put("javaImports", imports);
+          processTemplate(templateComputed, data, outWriter);
+        } catch (IOException e) {
+          log.error("Error generating file: " + classfileName, e);
+        } finally {
+          if (outWriter != null) {
+            try {
+              outWriter.close();
+            } catch (IOException ignore) {
+            }
+          }
         }
       }
     }
--- a/src/org/openbravo/base/gen/entity.ftl	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/base/gen/entity.ftl	Fri Jul 19 09:39:21 2013 +0200
@@ -26,7 +26,7 @@
  * under the License.
  * The Original Code is Openbravo ERP.
  * The Initial Developer of the Original Code is Openbravo SLU
- * All portions are Copyright (C) 2008-2011 Openbravo SLU
+ * All portions are Copyright (C) 2008-2013 Openbravo SLU
  * All Rights Reserved.
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -46,12 +46,23 @@
     public static final String TABLE_NAME = "${entity.tableName}";
     public static final String ENTITY_NAME = "${entity.name}";
     <#list entity.properties as p>
+    <#if !p.computedColumn>
     public static final String PROPERTY_${p.name?upper_case} = "${p.name}";
+    </#if>
     </#list>
+    
+    <#if entity.hasComputedColumns()>
+    // Computed columns properties, these properties cannot be directly accessed, they need
+    // to be read through _commputedColumns proxy. They cannot be directly used in HQL, OBQuery
+    // nor OBCriteria. 
+    <#list entity.computedColumnProperties as p>
+    public static final String COMPUTED_COLUMN_${p.name?upper_case} = "${p.name}";
+    </#list>
+    </#if>
 
     public ${entity.simpleClassName}() {
     <#list entity.properties as p>
-        <#if p.hasDefaultValue()>
+        <#if p.hasDefaultValue() && !p.computedColumn>
         setDefaultValue(PROPERTY_${p.name?upper_case}, ${p.formattedDefaultValue});
         </#if>
     </#list>
@@ -71,7 +82,11 @@
     <#if p.partOfCompositeId>
         return ((Id)getId()).«getter((Property)p)»();
     <#else>
+      <#if !p.computedColumn>
         return (${p.shorterTypeName}) get(PROPERTY_${p.name?upper_case});
+      <#else>
+        return (${p.shorterTypeName}) get(COMPUTED_COLUMN_${p.name?upper_case});
+      </#if>
     </#if>
     }
 
@@ -82,7 +97,11 @@
     <#if p.partOfCompositeId>
 	    ((Id)getId()).set${p.getterSetterName?cap_first}(${p.javaName});
 	<#else>
+      <#if !p.computedColumn>
         set(PROPERTY_${p.name?upper_case}, ${p.javaName});
+      <#else>
+        set(COMPUTED_COLUMN_${p.name?upper_case}, ${p.javaName});
+      </#if>
 	</#if>
     }
 
@@ -92,7 +111,12 @@
 	<#if p.oneToMany>
     @SuppressWarnings("unchecked")
     public ${theList(entity)}<${p.shorterNameTargetEntity}> get${p.name?cap_first}() {
-        return (${theList(entity)}<${p.shorterNameTargetEntity}>) get(PROPERTY_${p.name?upper_case});
+      <#if !p.computedColumn>
+      return (${theList(entity)}<${p.shorterNameTargetEntity}>) get(PROPERTY_${p.name?upper_case});        
+      <#else>
+      //ss
+      return (${theList(entity)}<${p.shorterNameTargetEntity}>) get(COMPUTED_COLUMN_${p.name?upper_case});
+      </#if>
     }
 
     public void set${p.getterSetterName?cap_first}(${theList(entity)}<${p.shorterNameTargetEntity}> ${p.name}) {
@@ -170,4 +194,17 @@
 		}		
 	}
 	</#if>
+	
+	<#if entity.hasComputedColumns()>
+    @Override
+    public Object get(String propName) {
+      <#list entity.computedColumnProperties as p>
+      if (COMPUTED_COLUMN_${p.name?upper_case}.equals(propName)){
+        return get_computedColumns().${getter(p)}();
+      }
+      </#list>
+    
+      return super.get(propName);
+    }
+    </#if>
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/base/gen/entityComputedColumns.ftl	Fri Jul 19 09:39:21 2013 +0200
@@ -0,0 +1,74 @@
+<#function getter p>
+  <#if p.boolean>
+    <#return "is" + p.getterSetterName?cap_first>
+  <#else>
+    <#return "get" + p.getterSetterName?cap_first>
+  </#if>
+</#function>
+
+<#function theList entity>
+  <#if entity.simpleClassName == "List">
+    <#return "java.util.List">
+  <#else>
+    <#return "List">
+  </#if>
+</#function>
+/*
+ *************************************************************************
+ * The contents of this file are subject to the Openbravo  Public  License
+ * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
+ * Version 1.1  with a permitted attribution clause; you may not  use this
+ * file except in compliance with the License. You  may  obtain  a copy of
+ * the License at http://www.openbravo.com/legal/license.html
+ * Software distributed under the License  is  distributed  on  an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific  language  governing  rights  and  limitations
+ * under the License.
+ * The Original Code is Openbravo ERP.
+ * The Initial Developer of the Original Code is Openbravo SLU
+ * All portions are Copyright (C) 2013 Openbravo SLU
+ * All Rights Reserved.
+ * Contributor(s):  ______________________________________.
+ ************************************************************************
+*/
+package ${entity.packageName};
+<#list javaImports as i>
+${i}
+</#list>
+/**
+ * Virtual entity class to hold computed columns for entity ${entity.name}.
+ *
+ * NOTE: This class should not be instantiated directly. To instantiate this
+ * class the {@link org.openbravo.base.provider.OBProvider} should be used.
+ */
+public class ${entity.simpleClassName}_ComputedColumns extends BaseOBObject implements ClientEnabled, OrganizationEnabled {
+    private static final long serialVersionUID = 1L;
+    public static final String ENTITY_NAME = "${entity.simpleClassName}_ComputedColumns";
+    
+    <#list properties as p>
+    public static final String PROPERTY_${p.name?upper_case} = "${p.name}";
+    </#list>
+
+    @Override
+    public String getEntityName() {
+        return ENTITY_NAME;
+    }
+
+    <#list properties as p>
+    <#if !p.oneToMany>
+    <#if p.name?matches("Id")>
+    @Override
+    </#if>
+    public ${p.shorterTypeName} ${getter(p)}() {
+      return (${p.shorterTypeName}) get(PROPERTY_${p.name?upper_case});
+    }
+
+    <#if p.name?matches("Id")>
+    @Override
+    </#if>
+    public void set${p.getterSetterName?cap_first}(${p.shorterTypeName} ${p.javaName}) {
+      set(PROPERTY_${p.name?upper_case}, ${p.javaName});
+    }
+    </#if>
+	</#list>
+}
--- a/src/org/openbravo/base/model/Entity.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/base/model/Entity.java	Fri Jul 19 09:39:21 2013 +0200
@@ -62,6 +62,7 @@
   private List<Property> identifierProperties;
   private List<Property> parentProperties;
   private List<Property> orderByProperties;
+  private List<Property> computedColumnProperties;
 
   private String name = null;
   private String tableName;
@@ -83,6 +84,7 @@
   private boolean isDeletable;
   private boolean isView;
   private boolean isDataSourceBased;
+  private boolean isVirtualEntity = false;
 
   private EntityValidator entityValidator;
   private AccessLevelChecker accessLevelChecker;
@@ -93,6 +95,8 @@
   private String treeType;
 
   private static final String DATASOURCEBASEDTABLE = "Datasource";
+  public static final String COMPUTED_COLUMNS_PROXY_PROPERTY = "_computedColumns";
+  public static final String COMPUTED_COLUMNS_CLASS_APPENDIX = "_ComputedColumns";
 
   public String getTreeType() {
     return treeType;
@@ -127,6 +131,8 @@
     identifierProperties = new ArrayList<Property>();
     parentProperties = new ArrayList<Property>();
     orderByProperties = new ArrayList<Property>();
+    computedColumnProperties = new ArrayList<Property>();
+
     // + 5 to take into account some additional properties for onetomany
     // and such
     propertiesByName = new HashMap<String, Property>(table.getColumns().size() + 5);
@@ -156,6 +162,22 @@
       if (p.isOrderByProperty()) {
         orderByProperties.add(p);
       }
+      if (p.getSqlLogic() != null) {
+        computedColumnProperties.add(p);
+      }
+    }
+
+    if (hasComputedColumns()) {
+      // entities with computed columns have an extra property to proxy access to computed columns
+      // so they are lazily calculated
+      Property p = new Property();
+      p.setIndexInEntity(properties.size());
+      p.setEntity(this);
+      p.setName(COMPUTED_COLUMNS_PROXY_PROPERTY);
+      p.setColumnName(COMPUTED_COLUMNS_PROXY_PROPERTY);
+      p.setProxy(true);
+      properties.add(p);
+      propertiesByName.put(COMPUTED_COLUMNS_PROXY_PROPERTY, p);
     }
 
     Collections.sort(identifierProperties, new Comparator<Property>() {
@@ -202,6 +224,67 @@
   }
 
   /**
+   * Checks if entity has any computed column
+   */
+  public boolean hasComputedColumns() {
+    return computedColumnProperties != null && computedColumnProperties.size() > 0;
+  }
+
+  public List<Property> getComputedColumnProperties() {
+    return computedColumnProperties;
+  }
+
+  public void initializeComputedColumns(Table t, Entity e) {
+    setTableName(t.getTableName() + "_CC");
+    setTableId(t.getId() + "_CC");
+    setClassName(e.getPackageName() + "." + e.getSimpleClassName()
+        + COMPUTED_COLUMNS_CLASS_APPENDIX);
+    setName(e.getSimpleClassName() + COMPUTED_COLUMNS_CLASS_APPENDIX);
+    setDeletable(false);
+    setMutable(false);
+    setInActive(true);
+    setView(false);
+    setModule(t.getThePackage().getModule());
+    isVirtualEntity = true;
+    properties = new ArrayList<Property>();
+    idProperties = new ArrayList<Property>();
+    identifierProperties = new ArrayList<Property>();
+    propertiesByName = new HashMap<String, Property>();
+    propertiesByColumnName = new HashMap<String, Property>();
+
+    for (final Column c : t.getColumns()) {
+      if (!(c.isKey() || c.getSqlLogic() != null
+          || "AD_Client_ID".equalsIgnoreCase(c.getColumnName()) || "AD_Org_ID".equalsIgnoreCase(c
+          .getColumnName()))) {
+        continue;
+      }
+      final Property p = new Property();
+      p.setEntity(this);
+      p.initializeFromColumn(c, false);
+      properties.add(p);
+      p.setIndexInEntity(properties.size() - 1);
+
+      propertiesByName.put(p.getName(), p);
+      if (p.getColumnName() != null) {
+        propertiesByColumnName.put(p.getColumnName().toLowerCase(), p);
+      }
+      if (p.isId()) {
+        idProperties.add(p);
+      }
+    }
+
+  }
+
+  /**
+   * Virtual entities are used when the main entity has computed columns. These virtual entities are
+   * mapped to the same database table the main entity is mapped to, they contain all the computed
+   * column properties, making in this way possible to lazily compute them.
+   */
+  public boolean isVirtualEntity() {
+    return isVirtualEntity;
+  }
+
+  /**
    * Add a property to the internal arrays of properties (common, identifier, etc.)
    * 
    * @param property
@@ -452,8 +535,26 @@
    * @throws CheckException
    */
   public Property getProperty(String propertyName) {
+    return getProperty(propertyName, true);
+  }
+
+  /**
+   * Retrieves the property using the propertyName. Throws a CheckException if no property exists
+   * with that name in case checkIsNotNull is true.
+   * 
+   * @param propertyName
+   *          the name used to search for the property.
+   * @param checkIsNotNull
+   *          if true, fails if property does not exists in entity, if false, returns null in this
+   *          case
+   * @return the found property
+   * @throws CheckException
+   */
+  public Property getProperty(String propertyName, boolean checkIsNotNull) {
     final Property prop = propertiesByName.get(propertyName);
-    Check.isNotNull(prop, "Property " + propertyName + " does not exist for entity " + this);
+    if (checkIsNotNull) {
+      Check.isNotNull(prop, "Property " + propertyName + " does not exist for entity " + this);
+    }
     return prop;
   }
 
@@ -532,6 +633,26 @@
     return properties;
   }
 
+  /**
+   * Gets a List of properties in the entity excluding, if present, the computed column proxy
+   * virtual property. If <code>includeComputed</code> parameter is <code>true</code>, all computed
+   * columns are also excluded.
+   * 
+   * @param includeComputed
+   *          should properties for computed columns be excluded from the list
+   * @return all the properties excluding proxy and, optionally, computed columns
+   */
+  public List<Property> getRealProperties(boolean includeComputed) {
+    List<Property> result = new ArrayList<Property>();
+    for (Property p : properties) {
+      if ((includeComputed || !p.isComputedColumn())
+          && !Entity.COMPUTED_COLUMNS_PROXY_PROPERTY.equals(p.getName())) {
+        result.add(p);
+      }
+    }
+    return result;
+  }
+
   public void setProperties(List<Property> properties) {
     this.properties = properties;
   }
@@ -680,6 +801,10 @@
   }
 
   List<String> getJavaImportsInternal() {
+    return getJavaImportsInternal(properties);
+  }
+
+  List<String> getJavaImportsInternal(List<Property> propertyList) {
     List<String> imports = new ArrayList<String>();
     Set<String> simpleImports = new HashSet<String>();
     imports.add("org.openbravo.base.structure.BaseOBObject");
@@ -703,7 +828,7 @@
     }
 
     // collect types of properties
-    for (Property p : properties) {
+    for (Property p : propertyList) {
       String fullType, simpleType;
       if (p.isOneToMany()) {
         // add list-type here to take precedence over model class named List
@@ -768,7 +893,11 @@
    * Used to generate java import statements during generate.entities
    */
   public List<String> getJavaImports() {
-    List<String> imports = getJavaImportsInternal();
+    return getJavaImports(properties);
+  }
+
+  public List<String> getJavaImports(List<Property> propertyList) {
+    List<String> imports = getJavaImportsInternal(propertyList);
     List<String> result = new ArrayList<String>();
     String lastImport = "";
     for (String i : imports) {
--- a/src/org/openbravo/base/model/ModelProvider.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/base/model/ModelProvider.java	Fri Jul 19 09:39:21 2013 +0200
@@ -229,6 +229,7 @@
       entitiesWithTreeType = new ArrayList<Entity>();
       for (final Table t : tables) {
         log.debug("Building model for table " + t.getName());
+
         final Entity e = new Entity();
         e.initialize(t);
         model.add(e);
@@ -241,6 +242,19 @@
         if (e.getTreeType() != null) {
           entitiesWithTreeType.add(e);
         }
+
+        if (e.hasComputedColumns()) {
+          // When the entity has computed columns, an extra virtual entity is generated in order to
+          // access these computed columns through a proxy that allows to compute them lazily.
+          log.debug("Generating computed columns proxy entity for entity " + e.getName());
+          final Entity computedColsEntity = new Entity();
+          computedColsEntity.initializeComputedColumns(t, e);
+
+          model.add(computedColsEntity);
+          entitiesByClassName.put(computedColsEntity.getClassName(), computedColsEntity);
+          entitiesByName.put(computedColsEntity.getName(), computedColsEntity);
+          entitiesByTableId.put(computedColsEntity.getTableId(), computedColsEntity);
+        }
       }
 
       // in the second pass set all the referenceProperties
@@ -270,14 +284,16 @@
         for (final Property p : e.getProperties()) {
           if (!p.isOneToMany()) {
             p.initializeName();
-            // don't do mandatory value setting for views or datasource based tables
-            if (!e.isView() && p.getColumnName() != null && !e.isDataSourceBased()) {
+            // don't do mandatory value setting for views, computed columns or datasource based
+            // tables
+            if (!e.isView() && p.getColumnName() != null && !e.isDataSourceBased()
+                && !e.isVirtualEntity()) {
               final Boolean mandatory = colMandatories.get(createColumnMandatoryKey(
                   e.getTableName(), p.getColumnName()));
               if (mandatory != null) {
                 p.setMandatory(mandatory);
-              } else if (p.getSqlLogic() == null) {
-                // only log in case the sql logic is not set
+              } else if (!p.isComputedColumn() && !p.isProxy() && !e.isVirtualEntity()) {
+                // only log in case the sql logic is not set and it is not a proxy
                 log.warn("Column " + p + " mandatory setting not found in the database metadata. "
                     + "A cause can be that the column does not exist in the database schema");
               }
@@ -494,8 +510,38 @@
       setReferencedPropertiesForTable(translatableColumns, t);
     }
 
+    // setting referenced properties from computed columns
+    for (final Entity entity : model) {
+      if (!entity.isVirtualEntity()) {
+        continue;
+      }
+
+      Entity baseEntity = entitiesByTableId.get(entity.getTableId().substring(0,
+          entity.getTableId().indexOf("_CC")));
+      if (baseEntity == null) {
+        log.warn("Not found base entity for computed column entity " + entity);
+        continue;
+      }
+
+      Table baseTable = tablesByTableName.get(baseEntity.getTableName().toLowerCase());
+      if (baseTable == null) {
+        log.warn("Not found base table for computed column entity " + entity);
+        continue;
+      }
+
+      for (Property p : entity.getProperties()) {
+        for (Column c : baseTable.getColumns()) {
+          if (c.getColumnName().equals(p.getColumnName())) {
+            if (!c.isPrimitiveType() && c.getReferenceType() != null) {
+              p.setReferencedProperty(c.getReferenceType().getProperty());
+            }
+            break;
+          }
+        }
+      }
+    }
+
     return translatableColumns;
-
   }
 
   private void setReferencedPropertiesForTable(List<Column> translatableColumns, final Table t) {
--- a/src/org/openbravo/base/model/Property.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/base/model/Property.java	Fri Jul 19 09:39:21 2013 +0200
@@ -122,6 +122,7 @@
 
   private Integer seqno;
   private boolean usedSequence;
+  private boolean isProxy;
 
   /**
    * Initializes this Property using the information from the Column.
@@ -130,7 +131,13 @@
    *          the column used to initialize this Property.
    */
   public void initializeFromColumn(Column fromColumn) {
-    fromColumn.setProperty(this);
+    initializeFromColumn(fromColumn, true);
+  }
+
+  void initializeFromColumn(Column fromColumn, boolean setPropertyInColumn) {
+    if (setPropertyInColumn) {
+      fromColumn.setProperty(this);
+    }
     setId(fromColumn.isKey());
     setIdentifier(fromColumn.isIdentifier());
     setParent(fromColumn.isParent());
@@ -192,7 +199,7 @@
     setInactive(!fromColumn.isActive());
 
     setModule(fromColumn.getModule());
-
+    isProxy = false;
   }
 
   // TODO: remove this hack when possible
@@ -604,6 +611,8 @@
       } else {
         typeName = getPrimitiveType().getName();
       }
+    } else if ("_computedColumns".equals(getColumnName())) {
+      return getEntity().getSimpleClassName() + "_ComputedColumns";
     } else if (getTargetEntity() == null) {
       log.warn("ERROR NO REFERENCETYPE " + getEntity().getName() + "." + getColumnName());
       return "java.lang.Object";
@@ -884,6 +893,27 @@
     this.isCompositeId = isCompositeId;
   }
 
+  /**
+   * A property is a computed column when it has sql logic, in this case it is calculated based on a
+   * sql formula and is accessed through a proxy.
+   */
+  public boolean isComputedColumn() {
+    return getSqlLogic() != null;
+  }
+
+  /**
+   * Proxy properties are used to access to computed columns. Computed columns are not directly
+   * within the entity they are defined in, but in a extra entity that is accessed through a proxy,
+   * in this way computed columns are lazily calculated.
+   */
+  public boolean isProxy() {
+    return isProxy;
+  }
+
+  public void setProxy(boolean isProxy) {
+    this.isProxy = isProxy;
+  }
+
   public List<Property> getIdParts() {
     return idParts;
   }
--- a/src/org/openbravo/dal/core/DalMappingGenerator.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/dal/core/DalMappingGenerator.java	Fri Jul 19 09:39:21 2013 +0200
@@ -21,9 +21,13 @@
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.commons.lang.StringEscapeUtils;
 import org.apache.log4j.Logger;
@@ -49,6 +53,7 @@
   private static final Logger log = Logger.getLogger(DalMappingGenerator.class);
 
   private final static String HIBERNATE_FILE_PROPERTY = "hibernate.hbm.file";
+  private final static String HIBERNATE_READ_FILE_PROPERTY = "hibernate.hbm.readFile";
 
   private final static String TEMPLATE_FILE = "template.hbm.xml";
   private final static String MAIN_TEMPLATE_FILE = "template_main.hbm.xml";
@@ -79,11 +84,30 @@
    * @return the generated Hibernate mapping (corresponds to what is found in a hbm.xml file)
    */
   public String generateMapping() {
+    final String hibernateFileLocation = OBPropertiesProvider.getInstance()
+        .getOpenbravoProperties().getProperty(HIBERNATE_FILE_PROPERTY);
+    final String readMappingFromFile = OBPropertiesProvider.getInstance().getOpenbravoProperties()
+        .getProperty(HIBERNATE_READ_FILE_PROPERTY);
+
+    if (hibernateFileLocation != null && readMappingFromFile != null
+        && Boolean.parseBoolean(readMappingFromFile)) {
+      try {
+        File hbm = new File(hibernateFileLocation);
+        if (hbm.exists()) {
+          log.info("Reading mapping from " + hibernateFileLocation);
+          FileInputStream fis = new FileInputStream(hbm);
+          return readFile(fis);
+        }
+      } catch (Exception e) {
+        log.error("Error reading mapping file, generating it instead", e);
+      }
+    }
+
     final ModelProvider mp = ModelProvider.getInstance();
     final StringBuilder sb = new StringBuilder();
     for (final Entity e : mp.getModel()) {
       // Do not map datasource based tables
-      if (!e.isDataSourceBased()) {
+      if (!e.isDataSourceBased() && !e.isVirtualEntity()) {
         final String entityMapping = generateMapping(e);
         sb.append(entityMapping);
       }
@@ -95,9 +119,6 @@
       log.debug(result);
     }
 
-    final String hibernateFileLocation = OBPropertiesProvider.getInstance()
-        .getOpenbravoProperties().getProperty(HIBERNATE_FILE_PROPERTY);
-
     if (hibernateFileLocation != null) {
       try {
         final File f = new File(hibernateFileLocation);
@@ -129,6 +150,7 @@
     // create the content by first getting the id
     final StringBuilder content = new StringBuilder();
 
+    content.append(TAB2);
     if (entity.getMappingClass() == null) {
       content.append("<tuplizer entity-mode=\"dynamic-map\" "
           + "class=\"org.openbravo.dal.core.OBDynamicTuplizer\"/>\n\n");
@@ -144,6 +166,7 @@
     }
     content.append(NL);
 
+    List<Property> computedColumns = new ArrayList<Property>();
     // now handle the standard columns
     for (final Property p : entity.getProperties()) {
       if (p.isId()) { // && p.isPrimitive()) { // handled separately
@@ -157,7 +180,9 @@
       if (p.isOneToMany()) {
         content.append(generateOneToMany(p));
       } else {
-        if (p.isPrimitive()) {
+        if (p.getSqlLogic() != null) {
+          computedColumns.add(p);
+        } else if (p.isPrimitive()) {
           content.append(generatePrimitiveMapping(p));
         } else {
           content.append(generateReferenceMapping(p));
@@ -165,14 +190,73 @@
       }
     }
 
+    if (!computedColumns.isEmpty()) {
+      // create a proxy property for all computed columns
+      content.append(generateComputedColumnsMapping(entity));
+    }
+
     if (entity.isActiveEnabled()) {
-      content.append(getActiveFilter());
+      content.append(TAB2 + getActiveFilter());
     }
 
     hbm = hbm.replace("content", content.toString());
+
+    if (!computedColumns.isEmpty()) {
+      hbm = hbm + generateComputedColumnsClassMapping(entity, computedColumns);
+    }
     return hbm;
   }
 
+  private String generateComputedColumnsClassMapping(Entity entity, List<Property> computedColumns) {
+    String hbm = getClassTemplateContents();
+    String entityName = getComputedColumnsEntityName(entity);
+    hbm = hbm.replaceAll("<class", "<class name=\"" + entity.getPackageName() + "." + entityName
+        + "\" ");
+    hbm = hbm.replaceAll("mappingName", entityName);
+    hbm = hbm.replaceAll("tableName", entity.getTableName());
+    hbm = hbm.replaceAll("ismutable", "false");
+
+    final StringBuilder content = new StringBuilder();
+    content.append(TAB2
+        + "<tuplizer entity-mode=\"pojo\" class=\"org.openbravo.dal.core.OBTuplizer\"/>" + NL + NL);
+    content.append(generateStandardID(entity) + NL);
+
+    content
+        .append(TAB2
+            + "<many-to-one name=\"client\" column=\"AD_Client_ID\" not-null=\"true\" update=\"false\" insert=\"false\" entity-name=\"ADClient\" access=\"org.openbravo.dal.core.OBDynamicPropertyHandler\"/>"
+            + NL);
+    content
+        .append(TAB2
+            + "<many-to-one name=\"organization\" column=\"AD_Org_ID\" not-null=\"true\" update=\"false\" insert=\"false\" entity-name=\"Organization\" access=\"org.openbravo.dal.core.OBDynamicPropertyHandler\"/>"
+            + NL + NL);
+
+    for (Property p : computedColumns) {
+      if (p.isPrimitive()) {
+        content.append(generatePrimitiveMapping(p));
+      } else {
+        content.append(generateReferenceMapping(p));
+      }
+    }
+    hbm = hbm.replace("content", content.toString());
+    return hbm;
+  }
+
+  private String generateComputedColumnsMapping(Entity entity) {
+    Check.isTrue(entity.getIdProperties().size() == 1,
+        "Computed columns are not supported in entities with composited ID");
+    StringBuffer sb = new StringBuffer();
+    final Property p = entity.getIdProperties().get(0);
+    sb.append(TAB2
+        + "<many-to-one name=\"_computedColumns\" update=\"false\" insert=\"false\" access=\"org.openbravo.dal.core.OBDynamicPropertyHandler\" ");
+    sb.append("column=\"" + p.getColumnName() + "\" ");
+    sb.append("entity-name=\"" + getComputedColumnsEntityName(entity) + "\"/>" + NL);
+    return sb.toString();
+  }
+
+  private String getComputedColumnsEntityName(Entity entity) {
+    return entity.getSimpleClassName() + "_ComputedColumns";
+  }
+
   private String getActiveFilter() {
     return "<filter name=\"activeFilter\" condition=\":activeParam = isActive\"/>\n";
   }
@@ -221,8 +305,12 @@
 
   private String generateReferenceMapping(Property p) {
     if (p.getTargetEntity() == null) {
-      return "<!-- Unsupported reference type " + p.getName() + " of entity "
-          + p.getEntity().getName() + "-->" + NL;
+      if (p.isProxy()) {
+        return "";
+      } else {
+        return "<!-- Unsupported reference type " + p.getName() + " of entity "
+            + p.getEntity().getName() + "-->" + NL;
+      }
     }
     final StringBuffer sb = new StringBuffer();
     if (p.isOneToOne()) {
@@ -238,6 +326,7 @@
       } else {
         sb.append("column=\"" + p.getColumnName() + "\"");
       }
+
       // cascade=\
       // "save-update\"
       if (p.isMandatory()) {
@@ -312,7 +401,7 @@
       sb.append(TAB3 + "<one-to-many entity-name=\"" + p.getTargetEntity().getName() + "\"/>" + NL);
 
       if (p.getTargetEntity().isActiveEnabled()) {
-        sb.append(getActiveFilter());
+        sb.append(TAB3 + getActiveFilter());
       }
       sb.append(TAB2 + "</bag>" + NL);
 
@@ -381,8 +470,12 @@
   }
 
   private String readFile(String fileName) {
+    return readFile(getClass().getResourceAsStream(fileName));
+  }
+
+  private String readFile(InputStream is) {
     try {
-      final InputStreamReader fr = new InputStreamReader(getClass().getResourceAsStream(fileName));
+      final InputStreamReader fr = new InputStreamReader(is);
       final BufferedReader br = new BufferedReader(fr);
       try {
         String line;
--- a/src/org/openbravo/dal/core/template.hbm.xml	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/dal/core/template.hbm.xml	Fri Jul 19 09:39:21 2013 +0200
@@ -2,3 +2,4 @@
 		<cache usage="read-write"/>
 content	
 	</class>
+
--- a/src/org/openbravo/dal/core/template_main.hbm.xml	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/dal/core/template_main.hbm.xml	Fri Jul 19 09:39:21 2013 +0200
@@ -13,7 +13,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2008-2011 Openbravo SLU 
+ * All portions are Copyright (C) 2008-2013 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
--- a/src/org/openbravo/dal/xml/EntityXMLConverter.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/dal/xml/EntityXMLConverter.java	Fri Jul 19 09:39:21 2013 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2008-2011 Openbravo SLU 
+ * All portions are Copyright (C) 2008-2013 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -90,6 +90,9 @@
   // controls if many-toreferences objects are also exported
   private boolean optionIncludeReferenced = false;
 
+  // controls if computed columns should be included in the exported data
+  private boolean includedComputedColumns = false;
+
   // controls if the children (mostly one-to-many) are also included
   // If the dataset is complete
   // (i.e. it contains all records of a set of records which refer to eachother)
@@ -349,7 +352,8 @@
       exportableProperties = DataSetService.getInstance().getExportableProperties(obObject, dst,
           dst.getDataSetColumnList(), optionExportTransientInfo);
     } else {
-      exportableProperties = new ArrayList<Property>(obObject.getEntity().getProperties());
+      exportableProperties = new ArrayList<Property>(obObject.getEntity().getRealProperties(
+          includedComputedColumns));
       if (excludeAuditInfo != null && excludeAuditInfo) {
         DataSetService.getInstance().removeAuditInfo(exportableProperties);
       }
@@ -611,6 +615,10 @@
     this.optionIncludeReferenced = optionIncludeReferenced;
   }
 
+  public void setIncludedComputedColumns(boolean includedComputedColumns) {
+    this.includedComputedColumns = includedComputedColumns;
+  }
+
   /**
    * Controls if children (the one-to-many associations) are exported. If true then the children can
    * be exported embedded in the parent or in the root of the xml. This is controlled by the
--- a/src/org/openbravo/dal/xml/StaxXMLEntityConverter.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/dal/xml/StaxXMLEntityConverter.java	Fri Jul 19 09:39:21 2013 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2009-2010 Openbravo SLU 
+ * All portions are Copyright (C) 2009-2013 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -162,6 +162,7 @@
       checkDanglingObjects();
       return result;
     } catch (XMLStreamException e) {
+      log.error("Error del horror", e);
       throw new EntityXMLException(e);
     }
   }
@@ -222,9 +223,10 @@
 
         // TODO: make this option controlled
         final boolean isNotImportableProperty = p.isTransient(bob)
-            || (p.isAuditInfo() && !isOptionImportAuditInfo()) || p.isInactive();
+            || (p.isAuditInfo() && !isOptionImportAuditInfo()) || p.isInactive()
+            || p.isComputedColumn();
         if (isNotImportableProperty) {
-          log.debug("Property " + p + " is inactive, transient or auditinfo, ignoring it");
+          log.debug("Property " + p + " is inactive, transient, computed or auditinfo, ignoring it");
           skipElement(xmlReader);
           continue;
         }
@@ -332,6 +334,7 @@
       return bob;
     } catch (final Exception e) {
       error("Exception when parsing entity " + entityName + " (" + id + "):" + e.getMessage());
+      log.error("Error processing entity " + entityName, e);
       return null;
     }
   }
--- a/src/org/openbravo/dal/xml/XMLEntityConverter.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/dal/xml/XMLEntityConverter.java	Fri Jul 19 09:39:21 2013 +0200
@@ -220,7 +220,8 @@
 
           // TODO: make this option controlled
           final boolean isNotImportableProperty = p.isTransient(bob)
-              || (p.isAuditInfo() && !isOptionImportAuditInfo()) || p.isInactive();
+              || (p.isAuditInfo() && !isOptionImportAuditInfo()) || p.isInactive() || p.isProxy()
+              || p.getEntity().isVirtualEntity();
           if (isNotImportableProperty) {
             log.debug("Property " + p + " is inactive, transient or auditinfo, " + "ignoring it");
             continue;
@@ -338,6 +339,7 @@
       }
     } catch (final Exception e) {
       error("Exception when parsing entity " + entityName + " (" + id + "):" + e.getMessage());
+      log.error("Exception when parsing entity " + entityName + " (" + id + ")", e);
       return null;
     }
   }
--- a/src/org/openbravo/service/db/DataImportService.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/service/db/DataImportService.java	Fri Jul 19 09:39:21 2013 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2008-2010 Openbravo SLU 
+ * All portions are Copyright (C) 2008-2013 Openbravo SLU 
  * All Rights Reserved. 
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -355,7 +355,7 @@
     int i = 0;
     final StringBuilder msgs = new StringBuilder();
     for (Property p : bob.getEntity().getProperties()) {
-      if (p.isOneToMany() || p.isAuditInfo() || p.isClientOrOrganization()) {
+      if (p.isOneToMany() || p.isAuditInfo() || p.isClientOrOrganization() || p.isComputedColumn()) {
         continue;
       }
       final Object value = bob.get(p.getName());
--- a/src/org/openbravo/service/rest/DalWebService.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/service/rest/DalWebService.java	Fri Jul 19 09:39:21 2013 +0200
@@ -11,7 +11,7 @@
  * under the License. 
  * The Original Code is Openbravo ERP. 
  * The Initial Developer of the Original Code is Openbravo SLU 
- * All portions are Copyright (C) 2008-2011 Openbravo SLU
+ * All portions are Copyright (C) 2008-2013 Openbravo SLU
  * All Rights Reserved.
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -203,6 +203,7 @@
             exc.setOptionIncludeReferenced(false);
             exc.setOptionExportClientOrganizationReferences(true);
             exc.setOutput(writer);
+            exc.setIncludedComputedColumns(true);
             // use the iterator because it can handle large data sets
             exc.setDataScroller(obq.scroll(ScrollMode.FORWARD_ONLY));
             exc.process(new ArrayList<BaseOBObject>());
--- a/src/org/openbravo/service/system/SystemService.java	Thu Aug 08 14:44:30 2013 +0200
+++ b/src/org/openbravo/service/system/SystemService.java	Fri Jul 19 09:39:21 2013 +0200
@@ -322,7 +322,7 @@
       List<Entity> entities = ModelProvider.getInstance().getModel();
       for (Entity entity : entities) {
         if ((entity.isClientEnabled() || entity.getName().equals("ADClient")) && !entity.isView()
-            && !entity.isDataSourceBased()) {
+            && !entity.isDataSourceBased() && !entity.isVirtualEntity()) {
           final String sql = "delete from " + entity.getTableName() + " where ad_client_id=?";
           sqlCommands.add(sql);
         }