com.wavemaker.tools.data.DataModelConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for com.wavemaker.tools.data.DataModelConfiguration.java

Source

/*
 *  Copyright (C) 2007-2012 VMware, Inc. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.wavemaker.tools.data;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeSet;
import java.util.concurrent.Callable;

import org.apache.commons.io.FileUtils;
import org.springframework.core.io.Resource;

import com.wavemaker.common.CommonConstants;
import com.wavemaker.common.NotYetImplementedException;
import com.wavemaker.common.WMRuntimeException;
import com.wavemaker.common.util.ObjectUtils;
import com.wavemaker.common.util.StringUtils;
import com.wavemaker.runtime.RuntimeAccess;
import com.wavemaker.runtime.client.TreeNode;
import com.wavemaker.runtime.data.DataServiceDefinition;
import com.wavemaker.runtime.data.DataServiceOperation;
import com.wavemaker.runtime.data.ExternalDataModelConfig;
import com.wavemaker.runtime.data.Input;
import com.wavemaker.runtime.data.util.DataServiceConstants;
import com.wavemaker.runtime.data.util.DataServiceUtils;
import com.wavemaker.runtime.data.util.QueryRunner;
import com.wavemaker.runtime.server.ServerConstants;
import com.wavemaker.runtime.service.definition.DeprecatedServiceDefinition;
import com.wavemaker.tools.common.ConfigurationException;
import com.wavemaker.tools.compiler.ProjectCompiler;
import com.wavemaker.tools.data.parser.BaseHbmParser;
import com.wavemaker.tools.data.parser.HbmParser;
import com.wavemaker.tools.data.parser.HbmQueryParser;
import com.wavemaker.tools.data.parser.HbmQueryWriter;
import com.wavemaker.tools.data.parser.HbmWriter;
import com.wavemaker.tools.data.spring.SpringService;
import com.wavemaker.tools.project.ProjectManager;
import com.wavemaker.tools.project.StudioFileSystem;
import com.wavemaker.tools.service.AbstractFileService;
import com.wavemaker.tools.service.ClassLoaderFactory;
import com.wavemaker.tools.service.DefaultClassLoaderFactory;
import com.wavemaker.tools.service.DesignServiceManager;
import com.wavemaker.tools.service.FileService;
import com.wavemaker.tools.service.codegen.ServiceDataObjectGenerator;
import com.wavemaker.tools.service.definitions.Service;
import com.wavemaker.tools.util.ResourceClassLoaderUtils;

/**
 * Manages a single Hibernate Data Model.
 * 
 * @author Simon Toens
 * @author Jeremy Grelle
 */
public class DataModelConfiguration {

    public static interface UpdateCallback {

        public void update(DataServiceDefinition dataServiceDefinition);
    }

    private static final String BACKUP_SUFFIX = ".bak";

    // writes new types for "select" queries
    private final OperationWrapperGenerator generator = new OperationWrapperGenerator();

    // data model name (same as serviceId)
    private final String name;

    // used for reading and writing files
    private final FileService fileService;

    // for initializing Hibernate
    private final ClassLoaderFactory classLoaderFactory;

    // relative path to dir containing cfg file
    private final String cfgPath;

    // cfg file name
    private final String cfgFile;

    // used for loading configuration data that is stored
    // externally
    private final ExternalDataModelConfig externalConfig;

    private final DataServiceSpringConfiguration springConfiguration;

    // data objects package
    private String dataPackage = null;

    // =================================================
    // entity related mappings

    // entity name to path of mapping file
    private final Map<String, String> entityNameToPath = new HashMap<String, String>();

    // entity name to EntityInfo
    private final Map<String, EntityInfo> entityInfos = new HashMap<String, EntityInfo>();

    private final Map<String, HbmParser> entityNameToHbmParser = new HashMap<String, HbmParser>();

    // value type name to TypeInfo
    private final Map<String, TypeInfo> valueTypes = new HashMap<String, TypeInfo>();

    // helper types (query output wrappers)
    private final Map<String, BeanInfo> outputTypes = new HashMap<String, BeanInfo>();

    private final Collection<EntityInfo> modifiedEntityInfos = new HashSet<EntityInfo>();

    // path to deleted entity
    private final Collection<String> deletedEntities = new HashSet<String>();

    // BaseHbmParser to path (has query and mapping parsers)
    private final Map<BaseHbmParser, String> parserToPath = new HashMap<BaseHbmParser, String>();

    // path to deleted data objects
    private final Collection<String> deletedTypes = new HashSet<String>();

    // =================================================
    // query related mappings

    private final List<String> queryFiles = new ArrayList<String>();

    // query name to QueryInfo
    private Map<String, QueryInfo> queryNameToQueryInfo = null;

    // for modifications
    // query name -> mapping-element - for modifying existing queries
    private Map<String, HbmQueryParser> queryNameToParser = null;

    // single mapping element for unknown output type
    private HbmQueryParser defaultQueryParser = null;

    // for writing
    // set of modified mapping elements
    private final Collection<HbmQueryParser> modifiedQueryParsers = new HashSet<HbmQueryParser>();

    // to keep track of what queries have been added, changed
    // used for logging
    private final Collection<String> modifiedQueryNames = new HashSet<String>();

    // path to deleted query file
    private final Collection<String> deletedQueries = new HashSet<String>();

    private final Collection<String> deletedSFQueries = new HashSet<String>(); // salesforce

    private final Collection<String> deletedSFDataObjects = new HashSet<String>(); // salesforce

    private StudioFileSystem fileSystem;

    private ProjectCompiler projectCompiler;

    private ProjectManager projMgr;

    // =================================================

    private final Collection<String> writtenBackupFiles = new HashSet<String>();

    public DataModelConfiguration(Resource cfgFile, FileService fileService, String serviceId,
            ExternalDataModelConfig externalConfig, ClassLoaderFactory classLoaderFactory) throws IOException {

        this.name = serviceId;
        this.cfgFile = cfgFile.getFilename();
        this.cfgPath = DesignServiceManager.getRuntimeRelativeDir(serviceId);
        this.fileService = fileService;
        this.externalConfig = externalConfig;
        this.classLoaderFactory = classLoaderFactory;
        this.springConfiguration = new DataServiceSpringConfiguration(fileService, this.cfgPath, this.cfgFile,
                serviceId);
        this.projMgr = (ProjectManager) RuntimeAccess.getInstance().getSession()
                .getAttribute(DataServiceConstants.CURRENT_PROJECT_MANAGER);

        this.projectCompiler = (ProjectCompiler) RuntimeAccess.getInstance().getSpringBean("projectCompiler");
        this.fileSystem = (StudioFileSystem) RuntimeAccess.getInstance().getSpringBean("fileSystem");

        if (!serviceId.equals(CommonConstants.SALESFORCE_SERVICE)) {
            setup();
        } else {
            setup_SF();
        }
    }

    // public DataModelConfiguration(StudioFileSystem fileSystem, Resource springConfig) {
    // this(fileSystem, springConfig, com.wavemaker.tools.data.util.DataServiceUtils.getDummyExternalConfig());
    // }

    public DataModelConfiguration(Resource springConfig) {
        this(springConfig, com.wavemaker.tools.data.util.DataServiceUtils.getDummyExternalConfig());
    }

    public DataModelConfiguration(Resource springConfig, ClassLoaderFactory classLoaderFactory) {
        this(springConfig, com.wavemaker.tools.data.util.DataServiceUtils.getDummyExternalConfig(),
                classLoaderFactory);
    }

    // public DataModelConfiguration(StudioFileSystem fileSystem, Resource springConfig, ExternalDataModelConfig
    // externalConfig) {
    // this(fileSystem, springConfig, externalConfig, new DefaultClassLoaderFactory(springConfig.getParentFile()), new
    // DefaultCompileService(
    // springConfig.getParentFile()));
    // }

    // public DataModelConfiguration(Resource springConfig, ExternalDataModelConfig externalConfig) {
    // this(springConfig, externalConfig, new DefaultClassLoaderFactory(fileSystem.getParent(springConfig)));
    // }

    public DataModelConfiguration(Resource springConfig, ExternalDataModelConfig externalConfig) {
        this.projectCompiler = (ProjectCompiler) RuntimeAccess.getInstance().getSpringBean("projectCompiler");
        this.fileSystem = (StudioFileSystem) RuntimeAccess.getInstance().getSpringBean("fileSystem");
        this.name = StringUtils.fromFirstOccurrence(springConfig.getFilename(), ".", -1);
        // this.cfgPath = ".";
        this.cfgPath = "";
        this.cfgFile = springConfig.getFilename();
        final Resource baseDir = this.fileSystem.getParent(springConfig);
        this.classLoaderFactory = new DefaultClassLoaderFactory(this.fileSystem.getParent(springConfig));
        this.externalConfig = externalConfig;
        this.fileSystem = this.fileSystem;
        this.projMgr = (ProjectManager) RuntimeAccess.getInstance().getSession()
                .getAttribute(DataServiceConstants.CURRENT_PROJECT_MANAGER);

        this.fileService = new AbstractFileService(this.fileSystem) {

            @Override
            public Resource getFileServiceRoot() {
                return baseDir;
            }
        };

        this.springConfiguration = new DataServiceSpringConfiguration(this.fileService, this.cfgPath, this.cfgFile,
                this.name);

        setup();
    }

    public DataModelConfiguration(Resource springConfig, ExternalDataModelConfig externalConfig,
            ClassLoaderFactory classLoaderFactory) {
        this.projectCompiler = (ProjectCompiler) RuntimeAccess.getInstance().getSpringBean("projectCompiler");
        this.fileSystem = (StudioFileSystem) RuntimeAccess.getInstance().getSpringBean("fileSystem");
        this.name = StringUtils.fromFirstOccurrence(springConfig.getFilename(), ".", -1);
        // this.cfgPath = ".";
        this.cfgPath = "";
        this.cfgFile = springConfig.getFilename();
        final Resource baseDir = this.fileSystem.getParent(springConfig);
        this.classLoaderFactory = classLoaderFactory;
        this.externalConfig = externalConfig;
        this.fileSystem = this.fileSystem;
        this.projMgr = (ProjectManager) RuntimeAccess.getInstance().getSession()
                .getAttribute(DataServiceConstants.CURRENT_PROJECT_MANAGER);

        this.fileService = new AbstractFileService(this.fileSystem) {

            @Override
            public Resource getFileServiceRoot() {
                return baseDir;
            }
        };

        this.springConfiguration = new DataServiceSpringConfiguration(this.fileService, this.cfgPath, this.cfgFile,
                this.name);

        setup();
    }

    // Mapping accessors
    // --------------------------------------------------------

    public Collection<String> getEntityNames() {
        return new TreeSet<String>(this.entityInfos.keySet());
    }

    public Collection<EntityInfo> getEntities() {
        return this.entityInfos.values();
    }

    public Collection<TypeInfo> getValueTypes() {
        return this.valueTypes.values();
    }

    public Collection<BeanInfo> getOtherTypes() {
        return this.outputTypes.values();
    }

    public BeanInfo getBean(String beanName) {
        return this.outputTypes.get(beanName);
    }

    public EntityInfo getEntity(String entity) {
        EntityInfo rtn = this.entityInfos.get(entity);
        if (rtn == null) {
            throw new ConfigurationException("Unknown entity: " + entity);
        }
        return rtn;
    }

    public String getEntityName(String javaType) {

        // questionable logic to get the type name
        // for a given java type
        return StringUtils.splitPackageAndClass(javaType).v2;
    }

    public boolean isKnownType(String javaType) {
        String s = getEntityName(javaType);
        return this.entityInfos.containsKey(s) || this.valueTypes.containsKey(s) || this.outputTypes.containsKey(s);
    }

    public boolean isEntityType(String javaType) {
        String s = getEntityName(javaType);
        return this.entityInfos.containsKey(s);
    }

    public boolean isValueType(String javaType) {
        String s = getEntityName(javaType);
        return this.valueTypes.containsKey(s);
    }

    public boolean isHelperType(String javaType) {
        String s = getEntityName(javaType);
        return this.outputTypes.containsKey(s);
    }

    public Collection<String> getPropertyNames(String entity) {
        return getEntity(entity).getPropertyNames();
    }

    public Collection<PropertyInfo> getProperties(String entity) {
        return getEntity(entity).getProperties();
    }

    public PropertyInfo getProperty(String entity, String property) {
        return getEntity(entity).getProperty(property);
    }

    public Collection<PropertyInfo> getRelatedProperties(String entity) {
        return getEntity(entity).getRelatedProperties();
    }

    public Collection<ColumnInfo> getColumns(String entity) {
        return getEntity(entity).getColumns();
    }

    public Map<String, ColumnInfo> getColumnsMap(String entity) {
        Collection<ColumnInfo> cols = getColumns(entity);
        Map<String, ColumnInfo> rtn = new LinkedHashMap<String, ColumnInfo>(cols.size());
        for (ColumnInfo c : cols) {
            rtn.put(c.getName(), c);
        }
        return rtn;
    }

    public ColumnInfo getColumn(String entity, String property) {
        return getProperty(entity, property).getColumn();
    }

    public ColumnInfo getColumn(String entity, String parentProperty, String property) {
        PropertyInfo parent = getProperty(entity, parentProperty);

        PropertyInfo p = parent.getCompositeProperty(property);
        if (p == null) {
            throw new ConfigurationException("Unkown property " + property + " on " + parentProperty);
        }
        return p.getColumn();
    }

    public Collection<String> getQueryNames() {
        return new TreeSet<String>(getHibernateQueries().keySet());
    }

    public Collection<QueryInfo> getQueries() {
        return getHibernateQueries().values();
    }

    public QueryInfo getQuery(String queryName) {
        return getHibernateQueries().get(queryName);
    }

    public QueryInfo getQueryByOperationName(String operationName) {
        QueryInfo rtn = getQuery(operationName);
        if (rtn == null) {
            operationName = DataServiceUtils.operationToQueryName(operationName, getHibernateQueries().keySet());
        }
        return getQuery(operationName);
    }

    public void updateQuery(QueryInfo queryInfo) {

        if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
            DataServiceLoggers.parserLogger.debug("updateQuery: " + queryInfo.getName() + " " + "inputs: "
                    + ObjectUtils.toString(queryInfo.getInputs()) + " " + "single result: "
                    + queryInfo.getReturnsSingleResult());
        }

        if (!queryInfo.getIsHQL()) {
            throw new NotYetImplementedException("Can only add HQL queries " + "for now");
        }

        if (queryInfo.getName() == null) {
            throw new IllegalArgumentException("Query must have a name set");
        }

        if (!StringUtils.isValidJavaIdentifier(queryInfo.getName())) {
            throw new ConfigurationException("Query name must be a valid Java identifier");
        }

        // make sure queries are initialized
        getHibernateQueries();

        HbmQueryParser parser = getParserForQuery(queryInfo);

        if (parser == null) {
            throw new AssertionError("Parser cannot be null");
        }

        // for DML operations, force single result
        if (DataServiceUtils.isDML(queryInfo.getQuery())) {
            if (!queryInfo.getReturnsSingleResult()) {
                if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                    DataServiceLoggers.parserLogger.info("updateQuery: " + queryInfo.getName()
                            + " is a DML operation, setting " + " returns single result");
                }
                queryInfo.setReturnsSingleResult(true);
            }
        }

        // updated by user, so no longer generated
        queryInfo.setIsGenerated(false);

        parser.getQueries().put(queryInfo.getName(), queryInfo);

        this.modifiedQueryParsers.add(parser);
        this.modifiedQueryNames.add(queryInfo.getName());
        this.queryNameToQueryInfo.put(queryInfo.getName(), queryInfo);
        this.queryNameToParser.put(queryInfo.getName(), parser);
    }

    public void addUserQueries(Collection<QueryInfo> queries) {

        for (QueryInfo q : queries) {
            if (!q.getIsGenerated()) {
                updateQuery(q);
            }
        }

    }

    public QueryInfo deleteQuery(String queryName) {
        return deleteQuery(queryName, false);
    }

    public QueryInfo deleteQuery(String queryName, boolean skipIfNotExists) {

        if (ObjectUtils.isNullOrEmpty(queryName)) {
            throw new IllegalArgumentException("queryName must be set");
        }

        // init
        getHibernateQueries();

        QueryInfo rtn = this.queryNameToQueryInfo.remove(queryName);

        if (rtn == null) {
            if (skipIfNotExists) {
                return null;
            } else {
                throw new IllegalArgumentException("Uknown query: " + queryName);
            }
        }

        this.modifiedQueryNames.remove(queryName);
        HbmQueryParser p = this.queryNameToParser.remove(queryName);
        this.modifiedQueryParsers.remove(p);

        p.getQueries().remove(queryName);

        if (p.getQueries().isEmpty() && p != this.defaultQueryParser) {
            p.close();
            String path = this.parserToPath.remove(p);
            this.deletedQueries.add(path);
            removeMappingFromSpringFile(path);
        } else {
            this.modifiedQueryParsers.add(p);
        }

        String outputType = rtn.getOutputType();

        // if it is one of our generated types, it lives in the output
        // package
        if (outputType != null && DataServiceUtils.isOutputType(outputType)) {
            this.deletedQueries.add(StringUtils.classNameToSrcFilePath(outputType));
            this.deletedSFQueries.add(queryName);
            this.deletedSFDataObjects.add(StringUtils.classNameToSrcFilePath(outputType));
        }

        return rtn;
    }

    public Collection<RelatedInfo> getRelated(String entity) {

        Collection<PropertyInfo> props = getRelatedProperties(entity);
        Collection<RelatedInfo> rtn = new ArrayList<RelatedInfo>(props.size());
        for (PropertyInfo p : props) {
            rtn.add(getRelated(entity, p.getName()));
        }
        return rtn;
    }

    public RelatedInfo getRelated(String entity, String property) {

        PropertyInfo p = getProperty(entity, property);

        String tableName = getEntity(entity).getTableName();
        if (p.getIsInverse()) {
            tableName = getEntity(p.getType()).getTableName();
        }

        return p.toRelated(tableName);
    }

    // End mapping accessors
    // --------------------------------------------------------

    // Start DataModel edit
    // --------------------------------------------------------

    /**
     * Mark all Entities as modified.
     */
    public void touchAllEntities() {
        for (EntityInfo e : getEntities()) {
            touchEntity(e);
        }
    }

    /**
     * Mark an Entity as modified.
     */
    public void touchEntity(EntityInfo entity) {
        this.modifiedEntityInfos.add(entity);
    }

    /**
     * Mark the Entity with given name as modified.
     */
    public void touchEntity(String entityName) {
        touchEntity(getEntity(entityName));
    }

    public void addValueType(TypeInfo typeInfo) {

        if (typeInfo == null) {
            throw new IllegalArgumentException("typeInfo must be set");
        }

        this.valueTypes.put(typeInfo.getName(), typeInfo);
    }

    public void deleteValueType(String valueTypeName) {

        if (ObjectUtils.isNullOrEmpty(valueTypeName)) {
            throw new IllegalArgumentException("valueTypeName must be set");
        }

        TypeInfo t = this.valueTypes.remove(valueTypeName);
        if (t == null) {
            throw new IllegalArgumentException("Unknown value type " + valueTypeName);
        }

        String path = getRelServicePath(StringUtils.classNameToSrcFilePath(t.getFullyQualifiedName()));

        this.deletedTypes.add(path);
    }

    // public void deleteEntity(String entityName) {
    // deleteEntity(entityName, false, null);
    // }

    // public void deleteEntity(String entityName, boolean updateReferences,
    // String newEntityName) {
    public void deleteEntity(String entityName) {

        if (ObjectUtils.isNullOrEmpty(entityName)) {
            throw new IllegalArgumentException("entityName must be set");
        }

        EntityInfo ei = this.entityInfos.remove(entityName);

        if (ei == null) {
            throw new IllegalArgumentException("Unknown entity: " + entityName);
        }

        this.modifiedEntityInfos.remove(ei);

        BaseHbmParser p = this.entityNameToHbmParser.remove(entityName);
        p.close();
        this.parserToPath.remove(p);
        String path = this.entityNameToPath.remove(entityName);
        removeMappingFromSpringFile(path);
        this.deletedEntities.add(path);

        String s = getRelServicePath(StringUtils.fromLastOccurrence(path, DataServiceConstants.HBM_EXT, -1));
        this.deletedTypes.add(s);

        // if (updateReferences) {
        // updateReferences(entityName, newEntityName, ei
        // .getRelatedProperties());
        // } else {
        deleteReferences(entityName, ei.getRelatedProperties());
        // }
    }

    private void replaceEntity(String entityName, String newEntityName) {

        if (ObjectUtils.isNullOrEmpty(entityName)) {
            throw new IllegalArgumentException("entityName must be set");
        }

        EntityInfo ei = this.entityInfos.remove(entityName);
        this.modifiedEntityInfos.remove(ei);

        ei.setEntityName(newEntityName);
        this.entityInfos.put(newEntityName, ei);

        String oldPkgName = StringUtils.packageToSrcFilePath(ei.getPackageName()) + "/" + entityName
                + DataServiceConstants.HBM_EXT;
        String oldPath = getProjectRoot() + "/" + getRelServicePath(oldPkgName);

        String newPkgName = StringUtils.packageToSrcFilePath(ei.getPackageName()) + "/" + newEntityName
                + DataServiceConstants.HBM_EXT;
        String newPath = getProjectRoot() + "/" + getRelServicePath(newPkgName);

        File oldf = new File(oldPath);
        File newf = new File(newPath);

        oldf.renameTo(newf);

        try {
            String content = FileUtils.readFileToString(newf, ServerConstants.DEFAULT_ENCODING);

            content = content.replace(entityName, newEntityName);
            FileUtils.writeStringToFile(newf, content, ServerConstants.DEFAULT_ENCODING);

            removeMappingFromSpringFile(oldPkgName);

            addEntityToSpringFile(newPkgName);
        } catch (IOException e) {
            throw new WMRuntimeException(e);
        }

        this.modifiedEntityInfos.add(ei);

        BaseHbmParser p = this.entityNameToHbmParser.remove(entityName);
        this.entityNameToHbmParser.put(newEntityName, (HbmParser) p);
        // p.close();
        this.parserToPath.remove(p);
        this.parserToPath.put(p, newPkgName);
        this.entityNameToPath.remove(entityName);
        this.entityNameToPath.put(newEntityName, newPkgName);
        this.deletedEntities.add(oldPkgName);

        String s = getRelServicePath(StringUtils.fromLastOccurrence(oldPkgName, DataServiceConstants.HBM_EXT, -1));
        this.deletedTypes.add(s);

        // Try to delete system generated queries if any.
        String qryName = "get" + entityName + "ById";
        deleteQuery(qryName, true);

        updateReferences(entityName, newEntityName, ei.getRelatedProperties());
    }

    private String getProjectRoot() {
        // ProjectManager projMgr = (ProjectManager)
        // RuntimeAccess.getInstance().getSession().getAttribute(DataServiceConstants.CURRENT_PROJECT_MANAGER);
        String projRoot;
        try {
            projRoot = this.projMgr.getCurrentProject().getProjectRoot().getURI().toString();
        } catch (IOException ex) {
            throw new WMRuntimeException(ex);
        }

        return projRoot;
    }

    public void updateEntity(String entityName, EntityInfo entity) {

        if (ObjectUtils.isNullOrEmpty(entityName)) {
            throw new IllegalArgumentException("entityName must be set");
        }

        EntityInfo orig;

        if (getEntityNames().contains(entityName)) {

            if (!entityName.equals(entity.getEntityName())) {

                validateName(entity.getEntityName());

                if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                    DataServiceLoggers.parserLogger.debug(entityName + " Processing entity rename from "
                            + entityName + " to " + entity.getEntityName());
                }

                // deleteEntity(entityName, true, entity.getEntityName());
                replaceEntity(entityName, entity.getEntityName());
                updateEntity(entity.getEntityName(), entity);
                return;
            }

            orig = getEntity(entityName);

            if (orig.isEqualTo(entity)) {
                if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                    DataServiceLoggers.parserLogger.debug(entityName + " has not changed");
                }
                return;
            } else {
                if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                    DataServiceLoggers.parserLogger.debug("Processing entity update for " + entityName);
                }
            }
        } else {

            validateName(entityName);

            orig = new EntityInfo();
            this.entityInfos.put(entityName, orig);
            String path = StringUtils.packageToSrcFilePath(entity.getPackageName()) + "/" + entityName
                    + DataServiceConstants.HBM_EXT;
            this.entityNameToPath.put(entityName, path);

            if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                DataServiceLoggers.parserLogger.debug("Processing entity add for " + entityName + " at " + path);
            }

            addEntityToSpringFile(path);

            // add dummy instance for mapping
            this.entityNameToHbmParser.put(entityName, new HbmParser());

            if (this.dataPackage == null) {
                // adding a new entity to an empty data model
                this.dataPackage = entity.getPackageName();
            }
        }

        String origSchema = orig.getSchemaName();
        String origCatalog = orig.getCatalogName();
        String origPackage = orig.getPackageName();

        orig.update(entity);
        this.modifiedEntityInfos.add(orig);

        propagateSchemaChange(orig, origSchema);
        propagateCatalogChange(orig, origCatalog);
        propagatePackageChange(orig, origPackage);

        setProperties(orig);
    }

    public void updateColumns(String entityName, List<ColumnInfo> columns) {
        updateColumns(entityName, columns, Collections.<PropertyInfo>emptyList());
    }

    public void updateColumns(String entityName, List<ColumnInfo> columns, List<PropertyInfo> properties) {

        EntityInfo ei = getEntity(entityName);

        if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
            DataServiceLoggers.parserLogger.info("Updating columns for " + entityName);
        }

        for (ColumnInfo ci : columns) {
            if (Integer.valueOf(0).equals(ci.getLength())) {
                ci.setLength(null);
            }
            if (Integer.valueOf(0).equals(ci.getPrecision())) {
                ci.setPrecision(null);
            }
        }

        ei.updateColumns(columns, properties, this);

        this.modifiedEntityInfos.add(ei);
    }

    public void updateRelated(String entityName, List<RelatedInfo> related) {

        EntityInfo ei = getEntity(entityName);

        List<PropertyInfo> rels = new ArrayList<PropertyInfo>(related.size());

        for (RelatedInfo ri : related) {

            validateName(ri.getName());

            PropertyInfo p = PropertyInfo.newCompositeProperty();
            p.fromRelated(ri, ei, this);
            rels.add(p);
        }

        Collection<EntityInfo> modifiedMany = ei.updateRelated(rels, this);
        this.modifiedEntityInfos.add(ei);
        this.modifiedEntityInfos.addAll(modifiedMany);

        ei.initFkColumnTypes(this);

        ei.setColumns(initColumns(entityName));
    }

    // End DataModel edit
    // --------------------------------------------------------

    public boolean useIndividualCRUDOperations() {
        return this.springConfiguration.useIndividualCRUDperations();
    }

    public void setRefreshEntities(List<String> refreshEntities) {
        this.springConfiguration.setRefreshEntities(refreshEntities);
    }

    public List<String> getRefreshEntities() {
        return this.springConfiguration.getRefreshEntities();
    }

    public synchronized Properties readConnectionProperties() {
        return readConnectionProperties(true);
    }

    public synchronized Properties readConnectionProperties(boolean removePrefix) {
        return this.springConfiguration.readCombinedProperties(removePrefix);
    }

    public synchronized void writeConnectionProperties(Properties props) {
        String connUrl = props.getProperty(DataServiceConstants.DB_URL_KEY);
        // ProjectManager projMgr = (ProjectManager)
        // RuntimeAccess.getInstance().getSession().getAttribute(DataServiceConstants.CURRENT_PROJECT_MANAGER);
        String projRoot;
        try {
            projRoot = this.projMgr.getCurrentProject().getWebAppRoot().getFile().getPath();
            connUrl = StringUtils.replacePlainStr(connUrl, projRoot, DataServiceConstants.WEB_ROOT_TOKEN);
            props.setProperty(DataServiceConstants.DB_URL_KEY, connUrl);

            this.springConfiguration.writeProperties(props);
        } catch (IOException e) {
            throw new WMRuntimeException(e);
        }
    }

    public void checkQuery(final String query, final Input[] inputs, final String values) {

        Runnable task = new Runnable() {

            @Override
            public void run() {

                QueryRunner queryRunner = SpringService.initQueryRunner(DataModelConfiguration.this.cfgFile);

                setBindParameters(queryRunner, inputs, values, true);

                try {
                    queryRunner.check(query);
                } finally {
                    queryRunner.dispose();
                }
            }
        };
        ResourceClassLoaderUtils.runInClassLoaderContext(task, this.classLoaderFactory.getClassLoader());
    }

    public Object runQuery(final String query, final Input[] inputs, final String values, final Long maxResults) {

        Callable<Object> task = new Callable<Object>() {

            @Override
            public Object call() {

                QueryRunner queryRunner = SpringService.initQueryRunner(DataModelConfiguration.this.cfgFile);

                setBindParameters(queryRunner, inputs, values, false);

                queryRunner.setMaxResults(maxResults);

                Object rtn = null;

                try {
                    rtn = queryRunner.run(query);
                } catch (Throwable th) {
                    throw DataServiceUtils.unwrap(th);
                } finally {
                    queryRunner.dispose();
                }

                return rtn;
            }
        };

        return ResourceClassLoaderUtils.runInClassLoaderContext(task, this.classLoaderFactory.getClassLoader());
    }

    public void save() {
        save(false);
    }

    public void save(boolean backup) {
        save(backup, com.wavemaker.tools.data.util.DataServiceUtils.NOOP_UPDATE_CALLBACK);
    }

    public void save(UpdateCallback updateCallback) {
        save(true, updateCallback);
    }

    public synchronized void save(boolean backup, UpdateCallback callback) {
        save(backup, callback, false, true);
    }

    public synchronized void save(boolean backup, UpdateCallback callback, boolean forceUpdate, boolean compile) {

        writeCfgFile(backup);

        boolean updateService = forceUpdate;

        if (this.modifiedQueryParsers.isEmpty() && this.deletedQueries.isEmpty()) {
            if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                DataServiceLoggers.parserLogger.info("No modified query, nothing to save");
            }
        } else {
            saveDeletedQueries(backup);
            saveModifiedQueries(backup);
            updateService = true;
        }

        if (this.modifiedEntityInfos.isEmpty() && this.deletedEntities.isEmpty()) {
            if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                DataServiceLoggers.parserLogger.info("No modified entities, nothing to save");
            }
        } else {
            saveModifiedEntities(backup, true);
            saveDeletedEntities(backup);
            updateService = true;
        }

        boolean cleanCompile = false;

        if (!this.deletedTypes.isEmpty()) {

            cleanCompile = true; // remove deleted .class files

            deleteTypes(true);

            removeServiceClass();
        }

        if (updateService) {
            if (compile) {
                this.projectCompiler.compile();
            }
            updateService(callback);
        }
    }

    public synchronized void revert() {
        revert(false);
    }

    public synchronized void revert(boolean compile) {

        Collection<String> s = new HashSet<String>(this.writtenBackupFiles);

        for (String path : s) {
            try {
                String original = this.fileService.readFile(path);
                String originalPath = StringUtils.fromLastOccurrence(path, BACKUP_SUFFIX, -1);
                this.fileService.writeFile(originalPath, original);
                this.fileService.deleteFile(path);
                if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                    DataServiceLoggers.parserLogger.info("Reverted " + originalPath + " from backup file");
                }
                this.writtenBackupFiles.remove(path);
            } catch (IOException ex) {
                throw new ConfigurationException(ex);
            }
        }

        this.springConfiguration.revert();

        if (compile) {
            this.projectCompiler.compile();
        }
    }

    public synchronized void removeBackupFiles() {

        Collection<String> s = new HashSet<String>(this.writtenBackupFiles);

        for (String path : s) {
            try {
                this.fileService.deleteFile(path);
            } catch (IOException ex) {
                throw new WMRuntimeException(ex);
            }
            this.writtenBackupFiles.remove(path);
        }
    }

    public List<String> getModifiedFiles() {
        List<String> rtn = new ArrayList<String>();
        for (EntityInfo ei : this.modifiedEntityInfos) {
            rtn.add(this.entityNameToPath.get(ei.getEntityName()));
        }
        for (HbmQueryParser p : this.modifiedQueryParsers) {
            rtn.add(this.parserToPath.get(p));
        }

        return rtn;
    }

    public void addDataObjectTree(TreeNode root) {
        for (TreeNode n : getTypeNodes()) {
            root.addChild(n);
        }
    }

    public void addQueryTree(TreeNode root) {
        for (TreeNode n : getQueryNodes()) {
            root.addChild(n);
        }
    }

    public List<TreeNode> getQueryNodes() {
        Collection<String> queryNames = getQueryNames();
        List<TreeNode> rtn = new ArrayList<TreeNode>(queryNames.size());
        for (String queryName : queryNames) {
            TreeNode n = new TreeNode(queryName);
            rtn.add(n);
            // query nodes don't have a 'type' annotation
            n.addData(queryName);
            n.addData(queryName);
        }
        return rtn;
    }

    public List<TreeNode> getTypeNodes() {

        Collection<String> entityNames = getEntityNames();
        List<TreeNode> rtn = new ArrayList<TreeNode>(entityNames.size());

        for (String entityName : entityNames) {
            TreeNode n2 = new TreeNode(entityName);
            EntityInfo ei = getEntity(entityName);
            n2.addData(com.wavemaker.runtime.data.util.DataServiceConstants.ENTITY_NODE);
            n2.addData(entityName);
            n2.addData(StringUtils.fq(ei.getPackageName(), entityName));
            rtn.add(n2);

            for (String propertyName : getPropertyNames(entityName)) {

                PropertyInfo p = getProperty(entityName, propertyName);

                TreeNode n3 = formatTreeNode(new TreeNode(), p);

                n2.addChild(n3);

                addCompositeProperties(p, n3);
            }
        }
        return rtn;
    }

    public List<String> getMappings() {
        return this.springConfiguration.getMappings();
    }

    public String getDataPackage() {
        if (this.name.equals(CommonConstants.SALESFORCE_SERVICE)) {
            return getDataPackage_SF();
        }

        if (this.dataPackage == null) {
            throw new IllegalStateException("Cannot determine package until mapping files have been registered");
        }
        return this.dataPackage;
    }

    public String getDataPackage_SF() {
        return "com.sforce.queries";
    }

    public String getOutputPackage() {
        return DataServiceUtils.getOutputPackage(getDataPackage());
    }

    /**
     * Init the runtime engine with the current configuration.
     * 
     * @throws RuntimeException if the current configuration is invalid.
     */
    public void initRuntime() {
        DeprecatedServiceDefinition def = null;
        try {
            def = initRuntimeDataServiceDefinition();
        } finally {
            if (def != null) {
                def.dispose();
            }
        }
    }

    public boolean compareTo(DataModelConfiguration other) {
        Collection<String> c = new HashSet<String>(getEntityNames());
        for (String s : other.getEntityNames()) {
            if (!c.remove(s)) {
                return false;
            }
        }
        if (!c.isEmpty()) {
            return false;
        }
        return true;
    }

    public boolean isKnownConfiguration() {
        return this.springConfiguration.isKnownConfiguration();
    }

    public void dispose() {
        for (BaseHbmParser p : this.entityNameToHbmParser.values()) {
            p.close();
        }
    }

    public void saveEntities() {
        saveModifiedEntities(false, false);
    }

    public void validateName(String name) {
        if (!StringUtils.isValidJavaIdentifier(name)) {
            throw new AssertionError("\"" + name + "\" is not a valid java identifier");
        }
    }

    @Override
    public String toString() {
        return "Mappings: " + this.entityNameToPath + "\n\nQueries: " + this.queryFiles;
    }

    public Collection<String> getDeletedSFQueries() {
        return this.deletedSFQueries;
    }

    public void removeDeletedSFQueries(String name) {
        this.deletedSFQueries.remove(name);
    }

    public Collection<String> getDeletedSFDataObjects() {
        return this.deletedSFDataObjects;
    }

    public void removeDeletedSFDataObjects(String name) {
        this.deletedSFDataObjects.remove(name);
    }

    private void setProperties(EntityInfo entity) {
        List<String> refreshEntities = getRefreshEntities();
        String fq = StringUtils.fq(entity.getPackageName(), entity.getEntityName());

        if (entity.isRefreshEntity() && !refreshEntities.contains(fq)) {
            refreshEntities.add(fq);
            setRefreshEntities(refreshEntities);
        } else if (!entity.isRefreshEntity() && refreshEntities.contains(fq)) {
            refreshEntities.remove(fq);
            setRefreshEntities(refreshEntities);
        }
    }

    private void deleteTypes(boolean backup) {

        for (String s : new HashSet<String>(this.deletedTypes)) {

            String path = s;

            if (!path.endsWith(DataServiceConstants.JAVA_EXT)) {
                path = path + DataServiceConstants.JAVA_EXT;
            }

            if (backup) {
                backup(path);
            }

            try {
                this.fileService.deleteFile(path);
            } catch (IOException ex) {
                throw new WMRuntimeException(ex);
            }

            this.deletedTypes.remove(s);
        }
    }

    private void updateReferences(String oldEntityName, String newEntityName,
            Collection<PropertyInfo> relatedProperties) {
        for (PropertyInfo pi : relatedProperties) {
            EntityInfo relatedEntity = getEntity(pi.getType());
            for (PropertyInfo relp : new ArrayList<PropertyInfo>(relatedEntity.getRelatedProperties())) {
                if (relp.getType().equals(oldEntityName)) {
                    if (relp.getIsInverse()) {
                        // will be added back by updateRelated
                        relatedEntity.removeProperty(relp);
                    } else {
                        relp.types(StringUtils.fq(getDataPackage(), newEntityName));
                    }
                    this.modifiedEntityInfos.add(relatedEntity);
                }
            }
        }
    }

    private void deleteReferences(String entityName, Collection<PropertyInfo> relatedProperties) {
        for (PropertyInfo pi : relatedProperties) {
            EntityInfo relatedEntity = getEntity(pi.getType());
            for (PropertyInfo relp : new ArrayList<PropertyInfo>(relatedEntity.getRelatedProperties())) {
                if (relp.getType().equals(entityName)) {
                    relatedEntity.removeProperty(relp);
                    this.modifiedEntityInfos.add(relatedEntity);
                }
            }
        }
    }

    private void writeCfgFile(boolean backup) {

        if (backup) {
            backup(this.springConfiguration.getPath());
        }

        this.springConfiguration.write();
    }

    private void propagateSchemaChange(EntityInfo entity, String origSchema) {

        for (EntityInfo other : getEntities()) {
            if (other == entity) {
                continue;
            }
            if (!String.valueOf(other.getSchemaName()).equals(String.valueOf(entity.getSchemaName()))
                    && String.valueOf(other.getSchemaName()).equals(String.valueOf(origSchema))) {
                other.setSchemaName(entity.getSchemaName());
                this.modifiedEntityInfos.add(other);
            }
        }
    }

    private void propagateCatalogChange(EntityInfo entity, String origCatalog) {

        for (EntityInfo other : getEntities()) {
            if (other == entity) {
                continue;
            }
            if (!String.valueOf(other.getCatalogName()).equals(String.valueOf(entity.getCatalogName()))
                    && String.valueOf(other.getSchemaName()).equals(String.valueOf(origCatalog))) {
                other.setCatalogName(entity.getCatalogName());
                this.modifiedEntityInfos.add(other);
            }
        }
    }

    private void propagatePackageChange(EntityInfo entity, String origPackage) {

        this.dataPackage = entity.getPackageName();

        for (EntityInfo other : getEntities()) {
            if (other == entity) {
                continue;
            }
            if (!other.getPackageName().equals(entity.getPackageName())
                    && other.getPackageName().equals(origPackage)) {
                other.setPackageName(entity.getPackageName());
                this.modifiedEntityInfos.add(other);
            }
        }
    }

    private void addQueryDocument(String path) {
        this.queryFiles.add(path);
    }

    private void addMappingDocument(String path) {

        EntityInfo ei = initEntityInfo(path);

        if (this.dataPackage == null) {
            this.dataPackage = ei.getPackageName();
        } else {
            if (!this.dataPackage.equals(ei.getPackageName())) {
                if (DataServiceLoggers.parserLogger.isWarnEnabled()) {
                    DataServiceLoggers.parserLogger
                            .warn(ei.getEntityName() + ": mapped class must match service class package: "
                                    + this.dataPackage + ". Mapped class: " + ei.getPackageName());

                }
            }
        }

        this.entityNameToPath.put(ei.getEntityName(), path);
        this.entityInfos.put(ei.getEntityName(), ei);
        ei.setColumns(initColumns(ei.getEntityName()));
    }

    private synchronized void saveDeletedEntities(boolean backup) {

        for (String path : new HashSet<String>(this.deletedEntities)) {

            if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                DataServiceLoggers.parserLogger.info("Deleting entity at " + path);
            }

            String relPath = getRelServicePath(path);

            if (backup) {
                backup(relPath);
            }

            try {
                this.fileService.deleteFile(relPath);
            } catch (IOException ex) {
                throw new WMRuntimeException(ex);
            }

            this.deletedEntities.remove(path);
        }

    }

    private synchronized void saveModifiedEntities(boolean backup, boolean generateJavaTypes) {

        for (EntityInfo ei : new HashSet<EntityInfo>(this.modifiedEntityInfos)) {

            if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                DataServiceLoggers.parserLogger.info("Updating entity " + ei.getEntityName());
            }

            String path = getRelServicePath(this.entityNameToPath.get(ei.getEntityName()));

            writeMappingResource(ei, path, backup);

            if (generateJavaTypes) {
                writeJavaType(ei, backup);
            }

            this.modifiedEntityInfos.remove(ei);
        }

    }

    private void writeJavaType(EntityInfo entity, boolean backup) {

        Collection<EntityInfo> c = new ArrayList<EntityInfo>(1);
        c.add(entity);
        Service s = com.wavemaker.tools.data.util.DataServiceUtils.toService(c);
        s.setId(this.name);

        // id types have to be Serializable and also
        // implement equals/hashCode
        Collection<String> serializableTypes = new HashSet<String>();
        for (EntityInfo ei : getEntities()) {
            PropertyInfo id = ei.getId();
            if (id.hasCompositeProperties()) {
                serializableTypes.add(id.getFullyQualifiedType());
            }
        }

        ServiceDataObjectGenerator gen = new ServiceDataObjectGenerator(s,
                DataServiceConstants.DEFAULT_COLLECTION_TYPE);

        gen.setSerializableTypes(serializableTypes);
        gen.setEqualsHashCodeTypes(serializableTypes);

        while (gen.hasNext()) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {

                String relPath = getTypePath(gen.getCurrentTypeName());

                if (backup) {
                    backup(relPath);
                }

                String path = getRelServicePath(relPath);
                gen.generateNext(baos);

                if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                    DataServiceLoggers.parserLogger.info("Writing java type " + relPath);
                }

                if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                    DataServiceLoggers.parserLogger.debug("File content==========================\n"
                            + baos.toString() + "\n==========================");
                }

                this.fileService.writeFile(path, baos.toString());
            } catch (IOException ex) {
                throw new ConfigurationException(ex);
            }
        }
    }

    private void writeMappingResource(EntityInfo ei, String path, boolean backup) {

        // we should write to a FileInputStream instead of memory
        StringWriter sw = new StringWriter();

        HbmWriter w = new HbmWriter(new PrintWriter(sw));

        w.setEntity(ei);

        w.write();

        try {

            if (backup) {
                backup(path);
            }

            if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                DataServiceLoggers.parserLogger.info("Writing mapping file " + path);
            }

            if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                DataServiceLoggers.parserLogger.debug("File content==========================\n" + sw.toString()
                        + "\n==========================");
            }

            this.fileService.writeFile(path, sw.toString());
        } catch (IOException ex) {
            throw new ConfigurationException(ex);
        }
    }

    private synchronized void saveDeletedQueries(boolean backup) {

        for (String path : new HashSet<String>(this.deletedQueries)) {

            String relPath = getRelServicePath(path);

            if (backup) {
                backup(relPath);
            }

            if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                DataServiceLoggers.parserLogger.info("Deleting query file at " + path);
            }

            try {
                this.fileService.deleteFile(relPath);
            } catch (IOException ex) {
                throw new WMRuntimeException(ex);
            }

            this.deletedQueries.remove(path);
        }

    }

    private synchronized void saveModifiedQueries(boolean backup) {

        Collection<HbmQueryParser> s = new HashSet<HbmQueryParser>(this.modifiedQueryParsers);

        for (HbmQueryParser p : s) {

            StringWriter sw = new StringWriter();

            HbmQueryWriter w = new HbmQueryWriter(new PrintWriter(sw));

            w.setMeta(p.getMeta());
            w.setQueries(p.getQueries().values());

            w.write();

            String path = getRelServicePath(this.parserToPath.get(p));

            if (backup) {
                backup(path);
            }

            if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                DataServiceLoggers.parserLogger.info("Writing query mapping file " + path);
            }

            String fileContent = sw.toString();

            this.modifiedQueryParsers.remove(p);

            try {
                this.fileService.writeFile(path, fileContent);
            } catch (IOException ex) {
                throw new ConfigurationException(ex);
            }

            if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                DataServiceLoggers.parserLogger.debug(
                        "File content==========================\n" + fileContent + "\n==========================");
            }
        }
    }

    private void updateService(UpdateCallback callback) {

        DeprecatedServiceDefinition def = null;

        try {

            def = initRuntimeDataServiceDefinition();

            DataServiceDefinition dataDef = com.wavemaker.tools.data.util.DataServiceUtils.unwrapAndCast(def);

            dataDef.getMetaData().setServiceClassName(this.externalConfig.getServiceClass());

            for (String q : this.modifiedQueryNames) {

                DataServiceOperation op = dataDef.getOperation(q);

                if (op.requiresResultWrapper()) {
                    generateWrapperType(op, true);
                }

                String in = dataDef.inputTypesToString(q);
                String out = dataDef.outputTypeToString(q);
                if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                    DataServiceLoggers.parserLogger
                            .debug("Updated query " + q + " InputTypes: " + in + " OutputType " + out);
                }

                QueryInfo qi = this.queryNameToQueryInfo.get(q);
                qi.setOutputType(out);
            }

            // set wrapper type names correctly for queries that
            // have not been modified
            for (String operationName : dataDef.getOperationNames()) {
                DataServiceOperation op = dataDef.getOperation(operationName);
                if (!op.isQuery() || !op.requiresResultWrapper()
                        || this.modifiedQueryNames.contains(op.getQueryName())) {
                    continue;
                }
                generateWrapperType(op, false);
            }

            // unnecessary - just keep a Collection of the fq name around
            Collection<String> fqTypeNames = new HashSet<String>(this.outputTypes.size());
            for (BeanInfo bi : this.outputTypes.values()) {
                fqTypeNames.add(bi.getFullyQualifiedName());
            }
            dataDef.getMetaData().setHelperClassNames(fqTypeNames);

            callback.update(dataDef);
        } finally {
            if (def != null) {
                def.dispose();
            }
        }

        this.modifiedQueryNames.clear();

    }

    private String getTypePath(String fqName) {
        return StringUtils.packageToSrcFilePath(fqName) + StringUtils.JAVA_SRC_EXT;
    }

    private void generateWrapperType(DataServiceOperation op, boolean write) {

        // make sure we have types with new name
        String oldPath = StringUtils.packageToSrcFilePath(
                DataServiceUtils.getOldOutputType(getDataPackage(), op.getName())) + StringUtils.JAVA_SRC_EXT;
        oldPath = getRelServicePath(oldPath);
        write = write || !new File(oldPath).exists();

        String fqName = DataServiceUtils.getOutputType(getDataPackage(), op.getName());

        if (write) {

            String outputWrapper = this.generator.generate(fqName, op);

            String path = getTypePath(fqName);

            if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                DataServiceLoggers.parserLogger.debug("Generating return type for " + op.getName() + ": " + path);
            }

            try {
                path = getRelServicePath(path);
                this.fileService.writeFile(path, outputWrapper);
            } catch (IOException ex) {
                throw new ConfigurationException(ex);
            }
        }

        op.setOutputType(fqName);
        this.outputTypes.put(StringUtils.splitPackageAndClass(fqName).v2, this.generator.getBeanInfo());

    }

    private void backup(String path) {
        try {
            String original = null;
            try {
                original = this.fileService.readFile(path);
            } catch (FileNotFoundException ex) {
                // if the original file doesn't exist (yet), then
                // we can't back it up
                return;
            }
            String backupPath = path + BACKUP_SUFFIX;
            if (DataServiceLoggers.parserLogger.isInfoEnabled()) {
                DataServiceLoggers.parserLogger.info("Writing backup file " + backupPath);
            }
            this.fileService.writeFile(backupPath, original);
            this.writtenBackupFiles.add(backupPath);
        } catch (IOException ex) {
            throw new ConfigurationException(ex);
        }
    }

    // requires all entities to be initialized
    private Map<String, TypeInfo> initValueTypesForEntity(EntityInfo ei) {

        Map<String, TypeInfo> rtn = new HashMap<String, TypeInfo>();

        if (ei.getProperties() == null) {
            return rtn;
        }

        List<PropertyInfo> props = new ArrayList<PropertyInfo>();
        props.addAll(ei.getProperties());

        while (!props.isEmpty()) {
            PropertyInfo pi = props.remove(0);
            // if this property doesn't map directly to a column,
            // and if its type is not an entity, it is a value type
            if (pi.getColumn() == null && !isEntityType(pi.getFullyQualifiedType())) {
                TypeInfo v = new TypeInfo(pi.getType(), ei.getPackageName());
                rtn.put(v.getName(), v);
            }
            if (pi.getCompositeProperties() != null) {
                props.addAll(pi.getCompositeProperties());
            }
        }

        return rtn;
    }

    private void addCompositeProperties(PropertyInfo p, TreeNode n) {

        if (p.getCompositeProperties() == null || p.getCompositeProperties().isEmpty()) {
            return;
        }

        for (PropertyInfo p2 : p.getCompositeProperties()) {
            TreeNode n2 = formatTreeNode(new TreeNode(), p2);
            n.addChild(n2);
            addCompositeProperties(p2, n2);
        }

    }

    // data has "node_type, prop_name, type, mapped col_name(for col only),
    // (PK|to-one|to-many|)(for prop only)
    private TreeNode formatTreeNode(TreeNode n, PropertyInfo p) {
        String content = p.getName() + " (" + p.getType() + ")";
        if (p.getIsId()) {
            n.setImage(DataServiceConstants.PK_IMAGE);
        }

        if (p.getIsRelated()) {
            n.addData(com.wavemaker.runtime.data.util.DataServiceConstants.RELATIONSHIP_NODE);
            if (p.getIsInverse()) {
                n.setImage(DataServiceConstants.TO_MANY_IMAGE);
            } else {
                n.setImage(DataServiceConstants.TO_ONE_IMAGE);
            }
        } else if (p.getColumn() == null) {
            n.addData(com.wavemaker.runtime.data.util.DataServiceConstants.PROPERTY_NODE);
        } else {
            n.addData(com.wavemaker.runtime.data.util.DataServiceConstants.COLUMN_NODE);
        }

        n.addData(p.getName());
        n.addData(p.getFullyQualifiedType());

        if (n.getData().get(0).equals(com.wavemaker.runtime.data.util.DataServiceConstants.COLUMN_NODE)) {
            n.addData(p.getColumn().getName());
        }

        if (p.getIsId()) {
            n.addData(PropertyInfo.PK_META);
        } else if (p.getIsRelated()) {
            if (p.getIsInverse()) {
                n.addData(RelatedInfo.Cardinality.OneToMany.toString());
            } else {
                n.addData(RelatedInfo.Cardinality.OneToOne.toString());
            }
        }

        n.setContent(content);

        return n;
    }

    private Map<String, ColumnInfo> initColumns(String entity) {

        Map<String, ColumnInfo> pkCols = new HashMap<String, ColumnInfo>();
        Map<String, ColumnInfo> fkCols = new HashMap<String, ColumnInfo>();

        Collection<ColumnInfo> allColumns = new LinkedHashSet<ColumnInfo>();

        for (PropertyInfo p : getProperties(entity)) {
            if (p.getIsRelated() && p.getIsInverse()) {
                continue;
            }
            for (ColumnInfo ci : p.allColumns()) {
                allColumns.add(ci);
                if (ci.getIsPk()) {
                    pkCols.put(ci.getName(), ci);
                }
                if (ci.getIsFk()) {
                    fkCols.put(ci.getName(), ci);
                }
            }
        }

        for (ColumnInfo fk : fkCols.values()) {
            if (pkCols.containsKey(fk.getName())) {
                ColumnInfo pk = pkCols.get(fk.getName());
                pk.setIsFk(true);
                allColumns.remove(fk);
            }
        }

        Map<String, ColumnInfo> rtn = new LinkedHashMap<String, ColumnInfo>(allColumns.size());

        for (ColumnInfo ci : allColumns) {
            rtn.put(ci.getName(), ci);
        }

        return rtn;
    }

    private void setup() {

        addRegisteredFiles();
    }

    private void setup_SF() {
        String path = "com/sforce/queries/sforce-queries.xml";
        addQueryDocument(path);
    }

    private HbmQueryParser getParserForQuery(QueryInfo qi) {

        if (this.queryNameToParser.containsKey(qi.getName())) {
            return this.queryNameToParser.get(qi.getName());
        }

        return getDefaultQueryParser();
    }

    private HbmQueryParser getDefaultQueryParser() {
        if (this.defaultQueryParser == null) {
            throw new ConfigurationException("No default query store " + "configured");
        }
        return this.defaultQueryParser;
    }

    private void addEntityToSpringFile(String path) {
        this.springConfiguration.addMapping(path);
    }

    private void removeMappingFromSpringFile(String path) {
        this.springConfiguration.removeMapping(path);
    }

    private synchronized Map<String, QueryInfo> getHibernateQueries() {

        if (this.queryNameToQueryInfo != null) {
            return this.queryNameToQueryInfo;
        }

        this.queryNameToQueryInfo = new HashMap<String, QueryInfo>();
        this.queryNameToParser = new HashMap<String, HbmQueryParser>();

        for (String path : this.queryFiles) {

            HbmQueryParser p = initHbmQueryParser(path);

            this.parserToPath.put(p, path);

            String meta = p.getMeta();

            if (meta != null && com.wavemaker.tools.data.util.DataServiceUtils.isDefaultQueryStore(meta)
                    || this.name.equals(CommonConstants.SALESFORCE_SERVICE)) {
                this.defaultQueryParser = p;
            }

            Collection<QueryInfo> queryInfos = p.getQueries().values();

            for (QueryInfo q : queryInfos) {

                if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                    DataServiceLoggers.parserLogger.debug("Processing HQL query: \"" + q.getName() + "\"");
                }

                this.queryNameToQueryInfo.put(q.getName(), q);
                this.queryNameToParser.put(q.getName(), p);

                String operationName = DataServiceUtils.queryToOperationName(q.getName());
                q.setReturnsSingleResult(this.externalConfig.returnsSingleResult(operationName));
                q.setOutputType(this.externalConfig.getOutputType(operationName));
            }
        }

        return this.queryNameToQueryInfo;
    }

    private HbmParser initHbmParser(String path) {

        String relpath = getRelServicePath(path);

        if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
            DataServiceLoggers.parserLogger.debug("Parsing mapping " + relpath);
        }

        String s = null;

        try {
            s = this.fileService.readFile(relpath);
        } catch (IOException ex) {
            throw new ConfigurationException(ex);
        }

        HbmParser rtn = new HbmParser(s);

        rtn.initAll();

        EntityInfo ei = rtn.getEntity();

        this.entityNameToHbmParser.put(ei.getEntityName(), rtn);

        return rtn;
    }

    private HbmQueryParser initHbmQueryParser(String path) {

        String relpath = getRelServicePath(path);

        if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
            DataServiceLoggers.parserLogger.debug("Parsing queries " + relpath);
        }

        String s = null;

        try {
            s = this.fileService.readFile(relpath);
        } catch (IOException ex) {
            throw new ConfigurationException(ex);
        }

        HbmQueryParser rtn = new HbmQueryParser(s);

        rtn.initAll();

        return rtn;
    }

    private EntityInfo initEntityInfo(String path) {

        HbmParser parser = initHbmParser(path);

        this.parserToPath.put(parser, path);

        EntityInfo rtn = parser.getEntity();

        String fq = StringUtils.fq(rtn.getPackageName(), rtn.getEntityName());

        rtn.setRefreshEntity(getRefreshEntities().contains(fq));

        return rtn;
    }

    // return rel path starting from service root dir
    private String getRelServicePath(String relPath) {
        StringBuilder sb = new StringBuilder(this.cfgPath.length() + relPath.length() + 1);
        // sb.append(this.cfgPath).append("/").append(relPath);
        sb.append(StringUtils.appendPaths(this.cfgPath, relPath));
        return sb.toString();
    }

    private void addRegisteredFiles() {

        List<String> mappings = this.springConfiguration.getMappings();

        for (String s : mappings) {

            try {
                if (s.endsWith(DataServiceConstants.HBM_EXT)) {
                    addMappingDocument(s);
                } else if (s.endsWith(DataServiceConstants.QUERY_EXT)) {
                    addQueryDocument(s);
                } else if (s.endsWith(".hql.xml")) { // old ext used in test
                    addQueryDocument(s);
                } else {
                    throw new ConfigurationException("Unknown resource type");
                }
            } catch (RuntimeException ex) {
                if (DataServiceLoggers.parserLogger.isWarnEnabled()) {
                    String msg = "Unable to process mapping resource \"" + s + "\"";
                    if (!ObjectUtils.isNullOrEmpty(ex.getMessage())) {
                        msg += ": " + ex.getMessage();
                    }
                    DataServiceLoggers.parserLogger.warn(msg);

                    if (DataServiceLoggers.parserLogger.isDebugEnabled()) {
                        DataServiceLoggers.parserLogger.debug(StringUtils.toString(ex));
                    }
                }
            }
        }

        initEntityInfos();

    }

    // do work here that requires all entities to be initialized
    private void initEntityInfos() {

        for (EntityInfo ei : getEntities()) {
            ei.initFkColumnTypes(this);
            this.valueTypes.putAll(initValueTypesForEntity(ei));
        }

    }

    private void removeServiceClass() {
        String packageName = StringUtils.splitPackageAndClass(this.externalConfig.getServiceClass()).v1;
        for (String s : com.wavemaker.tools.data.util.DataServiceUtils.getServiceClassNames(packageName,
                this.name)) {
            s = StringUtils.classNameToSrcFilePath(s);
            String path = getRelServicePath(s);
            try {
                this.fileService.deleteFile(path);
            } catch (IOException ex) {
                throw new WMRuntimeException(ex);
            }
        }
    }

    private DeprecatedServiceDefinition initRuntimeDataServiceDefinition() {

        DeprecatedServiceDefinition rtn = ResourceClassLoaderUtils
                .runInClassLoaderContext(new Callable<DeprecatedServiceDefinition>() {

                    @Override
                    public DeprecatedServiceDefinition call() {
                        return SpringService.initialize(DataModelConfiguration.this.cfgFile);
                    }
                }, this.classLoaderFactory.getClassLoader());

        return rtn;
    }

    private void setBindParameters(QueryRunner queryRunner, Input[] inputs, String values,
            boolean includeNullValues) {
        List<String> names = new ArrayList<String>(inputs.length);
        List<String> types = new ArrayList<String>(inputs.length);
        List<Boolean> isList = new ArrayList<Boolean>(inputs.length);

        for (int i = 0; i < inputs.length; i++) {
            names.add(inputs[i].getParamName());
            types.add(inputs[i].getParamType());
            isList.add(inputs[i].getList());
        }

        if (includeNullValues || !ObjectUtils.isNullOrEmpty(values)) {
            queryRunner.addBindParameters(names, types, values, isList);
        }
    }
}