org.openbravo.dal.core.DalMappingGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.openbravo.dal.core.DalMappingGenerator.java

Source

/*
 *************************************************************************
 * 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) 2008-2013 Openbravo SLU 
 * All Rights Reserved. 
 * Contributor(s):  ______________________________________.
 ************************************************************************
 */

package org.openbravo.dal.core;

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;
import org.hibernate.type.YesNoType;
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.provider.OBProvider;
import org.openbravo.base.provider.OBSingleton;
import org.openbravo.base.session.DalUUIDGenerator;
import org.openbravo.base.session.OBPropertiesProvider;
import org.openbravo.base.util.Check;

/**
 * This class is responsible for generating the Hibernate mapping for the tables and entities within
 * OpenBravo. It uses the runtime model provided by {@link ModelProvider ModelProvider}.
 * 
 * @author mtaal
 */

public class DalMappingGenerator implements OBSingleton {
    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";
    // private final static char TAB = '\t';
    private final static String TAB2 = "\t\t";
    private final static String TAB3 = "\t\t\t";
    private final static char NL = '\n';

    private static DalMappingGenerator instance = new DalMappingGenerator();

    public static synchronized DalMappingGenerator getInstance() {
        if (instance == null) {
            instance = OBProvider.getInstance().get(DalMappingGenerator.class);
        }
        return instance;
    }

    public static synchronized void setInstance(DalMappingGenerator dalMappingGenerator) {
        instance = dalMappingGenerator;
    }

    private String templateContents;

    /**
     * Generates the Hibernate mapping for {@link Entity Entities} in the system. The generated
     * Hibernate mapping is returned as a String.
     * 
     * @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);

        // If readMappingFromFile is true and the mapping is already generated to a file, this file will
        // be read instead of generating a new one. Useful while developing changes in mapping to edit
        // the file before generating it.
        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() && !e.isVirtualEntity()) {
                final String entityMapping = generateMapping(e);
                sb.append(entityMapping);
            }
        }
        final String mainTemplate = readFile(MAIN_TEMPLATE_FILE);
        final String result = mainTemplate.replace("contentPlaceholder", sb.toString());

        if (log.isDebugEnabled()) {
            log.debug(result);
        }

        if (hibernateFileLocation != null) {
            try {
                final File f = new File(hibernateFileLocation);
                if (f.exists()) {
                    f.delete();
                }
                f.createNewFile();
                final FileWriter fw = new FileWriter(f);
                fw.write(result);
                fw.close();
            } catch (final Exception e) {
                // ignoring exception for the rest
                log.error("Exception when saving hibernate mapping in " + hibernateFileLocation, e);
            }
        }
        return result;
    }

    private String generateMapping(Entity entity) {
        String hbm = getClassTemplateContents();
        hbm = hbm.replaceAll("mappingName", entity.getName());
        hbm = hbm.replaceAll("tableName", entity.getTableName());
        hbm = hbm.replaceAll("ismutable", entity.isMutable() + "");

        if (entity.getMappingClass() != null) {
            hbm = hbm.replaceAll("<class", "<class name=\"" + entity.getClassName() + "\" ");
        }

        // 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");
        } else {
            content.append("<tuplizer entity-mode=\"pojo\" " + "class=\"org.openbravo.dal.core.OBTuplizer\"/>\n\n");
        }

        if (entity.hasCompositeId()) {
            content.append(generateCompositeID(entity));
        } else {
            content.append(generateStandardID(entity));
        }
        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
                continue;
            }

            if (p.isPartOfCompositeId()) {
                continue;
            }

            if (p.isOneToMany()) {
                content.append(generateOneToMany(p));
            } else {
                if (p.getSqlLogic() != null) {
                    computedColumns.add(p);
                } else if (p.isPrimitive()) {
                    content.append(generatePrimitiveMapping(p));
                } else {
                    content.append(generateReferenceMapping(p));
                }
            }
        }

        if (!computedColumns.isEmpty()) {
            // create a proxy property for all computed columns
            content.append(generateComputedColumnsMapping(entity));
        }

        if (entity.isActiveEnabled()) {
            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";
    }

    private String generatePrimitiveMapping(Property p) {
        if (p.getHibernateType() == Object.class) {
            return "";
        }
        final StringBuffer sb = new StringBuffer();
        sb.append(TAB2 + "<property name=\"" + p.getName() + "\"");
        sb.append(getAccessorAttribute());
        String type;
        if (p.getHibernateType().isArray()) {
            type = p.getHibernateType().getComponentType().getName() + "[]";
        } else {
            type = p.getHibernateType().getName();
        }
        if (p.isBoolean()) {
            type = YesNoType.class.getName(); // "yes_no";
        }
        sb.append(" type=\"" + type + "\"");

        if (p.getSqlLogic() != null) {
            sb.append(" formula=\"" + StringEscapeUtils.escapeHtml(processSqlLogic(p.getSqlLogic())) + "\"");
        } else {
            sb.append(" column=\"" + p.getColumnName() + "\"");
        }

        if (p.isMandatory()) {
            sb.append(" not-null=\"true\"");
        }

        // ignoring isUpdatable for now as this is primarily used
        // for ui and not for background processes
        // if (!p.isUpdatable() || p.isInactive()) {

        if (p.isInactive() || p.getEntity().isView() || p.getSqlLogic() != null) {
            sb.append(" update=\"false\"");
            sb.append(" insert=\"false\"");
        }

        sb.append("/>" + NL);
        return sb.toString();
    }

    private String generateReferenceMapping(Property p) {
        if (p.getTargetEntity() == null) {
            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()) {
            final String name = p.getSimpleTypeName().substring(0, 1).toLowerCase()
                    + p.getSimpleTypeName().substring(1);
            sb.append(TAB2 + "<one-to-one name=\"" + name + "\"");
            sb.append(" constrained=\"true\"");
        } else {
            sb.append(TAB2 + "<many-to-one name=\"" + p.getName() + "\" ");
            if (p.getSqlLogic() != null) {
                sb.append("formula=\"" + StringEscapeUtils.escapeHtml(processSqlLogic(p.getSqlLogic())) + "\"");
            } else {
                sb.append("column=\"" + p.getColumnName() + "\"");
            }

            // cascade=\
            // "save-update\"
            if (p.isMandatory()) {
                sb.append(" not-null=\"true\"");
            }

            // language is always loaded explicitly by Hibernate because it is a non-pk
            // association, eager fetch with the parent then..
            // after some more thought, normally only a limited number of languages are used
            // resulting in a few extra queries in the beginning of the transaction, the rest is loaded
            // from the first level cache, so the current approach is fine, keep the following
            // lines commented
            // if (p.getTargetEntity() != null && p.getTargetEntity().getName().equals("ADLanguage")) {
            // sb.append(" fetch=\"join\"");
            // }

            if (p.isInactive() || p.getEntity().isView()) {
                sb.append(" update=\"false\"");
            }
            if (p.isInactive() || p.getEntity().isView()) {
                sb.append(" insert=\"false\"");
            }
        }
        // sb.append(" cascade=\"save-update\"");

        // to prevent cascade errors that the parent is saved after the child
        // this is handled by the DataImportService.insertObjectGraph
        // but other specific code needs it
        if (p.isParent() && p.isMandatory()) {
            sb.append(" cascade=\"persist\"");
        }

        sb.append(" entity-name=\"" + p.getTargetEntity().getName() + "\"");

        sb.append(getAccessorAttribute());

        if (p.getReferencedProperty() != null && !p.getReferencedProperty().isId()) {
            sb.append(" property-ref=\"" + p.getReferencedProperty().getName() + "\"");
        }

        sb.append("/>" + NL);
        return sb.toString();
    }

    private String generateOneToMany(Property p) {
        final StringBuffer sb = new StringBuffer();
        StringBuffer order = new StringBuffer();
        if (p.isOneToMany()) {
            if (p.getTargetEntity().getOrderByProperties().size() > 0) {
                order.append("order-by=\"");
                for (final Property po : p.getTargetEntity().getOrderByProperties()) {
                    order.append(po.getColumnName() + " ASC,");
                }
                order = order.replace(order.length() - 1, order.length(), "");
                order.append("\"");
            }

            String mutable = "";
            String cascade = "";
            if (p.isChild()) {
                cascade = " cascade=\"all,delete-orphan\" ";
            }
            if (p.getEntity().isView() || p.getTargetEntity().isView()) {
                mutable = " mutable=\"false\" ";
                cascade = "";
            }

            sb.append(TAB2 + "<bag name=\"" + p.getName() + "\" " + cascade + order + getAccessorAttribute()
                    + mutable + " inverse=\"true\">" + NL);
            sb.append(TAB3 + "<key column=\"" + p.getReferencedProperty().getColumnName() + "\""
                    + (p.getReferencedProperty().isMandatory() ? " not-null=\"true\"" : "") + "/>" + NL);
            sb.append(TAB3 + "<one-to-many entity-name=\"" + p.getTargetEntity().getName() + "\"/>" + NL);

            if (p.getTargetEntity().isActiveEnabled()) {
                sb.append(TAB3 + getActiveFilter());
            }
            sb.append(TAB2 + "</bag>" + NL);

        }
        return sb.toString();
    }

    // assumes one primary key column
    private String generateStandardID(Entity entity) {
        Check.isTrue(entity.getIdProperties().size() == 1,
                "Method can only handle primary keys with one column in entity " + entity.getName() + ". It has "
                        + entity.getIdProperties().size());
        final Property p = entity.getIdProperties().get(0);
        final StringBuffer sb = new StringBuffer();
        sb.append(TAB2 + "<id name=\"" + p.getName() + "\" type=\"string\" " + getAccessorAttribute() + " column=\""
                + p.getColumnName() + "\" unsaved-value=\"null\">" + NL);
        if (p.getIdBasedOnProperty() != null) {
            sb.append(TAB3 + "<generator class=\"foreign\">" + NL);
            sb.append(TAB2 + TAB2 + "<param name=\"property\">" + p.getIdBasedOnProperty().getName() + "</param>"
                    + NL);
            sb.append(TAB3 + "</generator>" + NL);
        } else if (p.isUuid()) {
            sb.append(TAB3 + "<generator class=\"" + DalUUIDGenerator.class.getName() + "\"/>" + NL);
        }
        sb.append(TAB2 + "</id>" + NL);
        return sb.toString();
    }

    private String getAccessorAttribute() {
        return " access=\"" + OBDynamicPropertyHandler.class.getName() + "\"";
    }

    private String generateCompositeID(Entity e) {
        Check.isTrue(e.hasCompositeId(), "Method can only handle primary keys with more than one column");
        final StringBuffer sb = new StringBuffer();
        sb.append(TAB2 + "<composite-id name=\"id\" class=\"" + e.getClassName() + "$Id\"" + getAccessorAttribute()
                + ">" + NL);
        final Property compId = e.getIdProperties().get(0);
        Check.isTrue(compId.isCompositeId(), "Property " + compId + " is expected to be a composite Id");
        for (final Property p : compId.getIdParts()) {
            if (p.isPrimitive()) {
                String type = p.getHibernateType().getName();
                if (boolean.class.isAssignableFrom(p.getHibernateType().getClass())
                        || Boolean.class == p.getHibernateType()) {
                    type = "yes_no";
                }
                sb.append(TAB3 + "<key-property name=\"" + p.getName() + "\" column=\"" + p.getColumnName()
                        + "\" type=\"" + type + "\"/>" + NL);
            } else {
                sb.append(TAB3 + "<key-many-to-one name=\"" + p.getName() + "\" column=\"" + p.getColumnName()
                        + "\"");
                sb.append(" entity-name=\"" + p.getTargetEntity().getName() + "\"");
                sb.append("/>" + NL);
            }
        }
        sb.append(TAB2 + "</composite-id>" + NL);
        return sb.toString();
    }

    private String getClassTemplateContents() {
        if (templateContents == null) {
            templateContents = readFile(TEMPLATE_FILE);
        }
        return templateContents;
    }

    private String readFile(String fileName) {
        return readFile(getClass().getResourceAsStream(fileName));
    }

    private String readFile(InputStream is) {
        try {
            final InputStreamReader fr = new InputStreamReader(is);
            final BufferedReader br = new BufferedReader(fr);
            try {
                String line;
                final StringBuffer sb = new StringBuffer();
                while ((line = br.readLine()) != null) {
                    sb.append(line + "\n");
                }
                return sb.toString();
            } finally {
                br.close();
                fr.close();
            }
        } catch (final IOException e) {
            throw new OBException(e);
        }
    }

    private String processSqlLogic(String val) {
        String localVal = val.trim();
        if (val.contains("\"")) {
            localVal = localVal.replace("\"", "");
        }
        if (!localVal.startsWith("(")) {
            localVal = "(" + localVal;
        }
        if (!localVal.endsWith(")")) {
            localVal = localVal + ")";
        }
        return localVal;
    }
}