/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.tools.schemaframework;

import java.io.*;
import java.util.*;
import java.sql.Time;
import org.eclipse.persistence.mappings.*;
import org.eclipse.persistence.sequencing.TableSequence;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.DatabaseLogin;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.internal.queries.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.expressions.ParameterExpression;

/**
 * <b>Purpose</b>: To generate StoredProcedures from EclipseLink Projects <p>
 * <b>Description</b>: This Class was designed to read in a project and produce StoredProcedures.
 *  It then modifies the descriptors files of the project to use these StoredProcedures.
 * NOTE: reads are not supported in Oracle.
 *    <p>
 * <b>Responsibilities</b>:<ul>
 * <li>
 * </ul>
 * @since TopLink 2.1
 * @author Gordon Yorke
 */
public class StoredProcedureGenerator {
    public SchemaManager schemaManager;
    /** This hashtable is used to store the storedProcedure referenced by the class name. */
    private Hashtable storedProcedures;
    /** This hashtable is used to store the storedProcedure referenced by the mapping name. */
    private Hashtable mappingStoredProcedures;
    private Hashtable intToTypeConverterHash;
    private Writer writer;
    private String prefix;
    private static final String DEFAULT_PREFIX = "";
    private Hashtable sequenceProcedures;
    private static final int MAX_NAME_SIZE = 30;

    public StoredProcedureGenerator(SchemaManager schemaMngr) {
        super();
        this.schemaManager = schemaMngr;
        this.sequenceProcedures = new Hashtable();
        this.storedProcedures = new Hashtable();
        this.mappingStoredProcedures = new Hashtable();
        this.buildIntToTypeConverterHash();
        this.prefix = DEFAULT_PREFIX;
        this.verify();
    }

    /**
     * INTERNAL: Build all conversions based on JDBC return values.
     */
    protected void buildIntToTypeConverterHash() {
        this.intToTypeConverterHash = new Hashtable();
        this.intToTypeConverterHash.put(Integer.valueOf(8), Double.class);
        this.intToTypeConverterHash.put(Integer.valueOf(-7), Boolean.class);
        this.intToTypeConverterHash.put(Integer.valueOf(-3), Byte[].class);
        this.intToTypeConverterHash.put(Integer.valueOf(-6), Short.class);
        this.intToTypeConverterHash.put(Integer.valueOf(5), Short.class);
        this.intToTypeConverterHash.put(Integer.valueOf(4), Integer.class);
        this.intToTypeConverterHash.put(Integer.valueOf(2), java.math.BigDecimal.class);
        this.intToTypeConverterHash.put(Integer.valueOf(6), Float.class);
        this.intToTypeConverterHash.put(Integer.valueOf(1), Character.class);
        this.intToTypeConverterHash.put(Integer.valueOf(12), String.class);
        this.intToTypeConverterHash.put(Integer.valueOf(91), java.sql.Date.class);
        this.intToTypeConverterHash.put(Integer.valueOf(93), java.sql.Timestamp.class);
        this.intToTypeConverterHash.put(Integer.valueOf(3), java.math.BigDecimal.class);

        this.intToTypeConverterHash.put(Integer.valueOf(-5), java.math.BigDecimal.class);
        this.intToTypeConverterHash.put(Integer.valueOf(7), Float.class);
        this.intToTypeConverterHash.put(Integer.valueOf(-1), String.class);
        this.intToTypeConverterHash.put(Integer.valueOf(92), Time.class);
        this.intToTypeConverterHash.put(Integer.valueOf(-2), Byte[].class);
        this.intToTypeConverterHash.put(Integer.valueOf(-4), Byte[].class);
        //this.intToTypeConverterHash.put(Integer.valueOf(0),null.class);
    }

    /**
     * INTERNAL: Given a call, this method produces the stored procedure string
     * based on the SQL string inside the call.
     */
    protected String buildProcedureString(SQLCall call) {
        String stringToModify = call.getSQLString();
        String replacementToken = getSession().getPlatform().getStoredProcedureParameterPrefix();
        StringWriter stringWriter = new StringWriter();
        int startIndex = 0;
        int nextParamIndex = 0;
        int tokenIndex = stringToModify.indexOf("?");

        while (tokenIndex != -1) {
            stringWriter.write(stringToModify.substring(startIndex, tokenIndex));
            startIndex = tokenIndex + 1;
            Object parameter = call.getParameters().get(nextParamIndex);
            if (parameter instanceof DatabaseField) {
                stringWriter.write(replacementToken);
                stringWriter.write(((DatabaseField)parameter).getName());
            } else if (parameter instanceof ParameterExpression) {
                stringWriter.write(replacementToken);
                stringWriter.write(((ParameterExpression)parameter).getField().getName());
            } else {
                getSession().getPlatform().appendParameter(call, stringWriter, parameter);
            }

            tokenIndex = stringToModify.indexOf("?", startIndex);
            nextParamIndex++;
        }
        stringWriter.write(stringToModify.substring(startIndex));

        return stringWriter.toString();
    }

    /**
     * PUBLIC: Generate an amendment class that will set up the descriptors to use
     * these stored procedures.
     */
    public void generateAmendmentClass(Writer outputWriter, String packageName, String className) throws ValidationException {
        String methodComment = "/**\n * EclipseLink generated method. \n * <b>WARNING</b>: This code was generated by an automated tool.\n * Any changes will be lost when the code is re-generated\n */";
        ClassDescriptor descriptor;
        Vector storedProcedureVector;
        Hashtable mappingHashtable;
        StoredProcedureDefinition definition;
        Vector storedProcedureDefinitionArguments;
        Enumeration argumentEnum;
        FieldDefinition fieldDefinition;
        try {
            outputWriter.write("package ");
            outputWriter.write(packageName);
            outputWriter.write(";\n\nimport java.util.*;\nimport java.lang.reflect.*;");
            outputWriter.write("\nimport org.eclipse.persistence.queries.*;\nimport org.eclipse.persistence.sessions.*;\nimport org.eclipse.persistence.mappings.*;\n\n/**\n * ");
            outputWriter.write("This is a EclipseLink generated class to add stored procedure admendments to a project.  \n * Any changes to this code will be lost when the class is regenerated \n */\npublic class ");
            outputWriter.write(className);
            outputWriter.write("{\n");
            Enumeration descriptorEnum = this.storedProcedures.keys();
            while (descriptorEnum.hasMoreElements()) {
                descriptor = (ClassDescriptor)descriptorEnum.nextElement();
                if (descriptor.isDescriptorForInterface() || descriptor.isAggregateDescriptor()) {
                    continue;
                }
                outputWriter.write(methodComment);
                outputWriter.write("\npublic static void amend");
                outputWriter.write(Helper.getShortClassName(descriptor.getJavaClass()));
                outputWriter.write("ClassDescriptor(ClassDescriptor descriptor){\n\t");
                storedProcedureVector = (Vector)this.storedProcedures.get(descriptor);
                mappingHashtable = (Hashtable)this.mappingStoredProcedures.get(descriptor);
                definition = (StoredProcedureDefinition)storedProcedureVector.elementAt(0);
                outputWriter.write("\n\t//INSERT QUERY\n");
                outputWriter.write("\tInsertObjectQuery insertQuery = new InsertObjectQuery();\n\tStoredProcedureCall call = new StoredProcedureCall();\n");
                outputWriter.write("\tcall.setProcedureName(\"");
                outputWriter.write(definition.getName());
                outputWriter.write("\");\n\t");
                storedProcedureDefinitionArguments = definition.getArguments();
                argumentEnum = storedProcedureDefinitionArguments.elements();

                while (argumentEnum.hasMoreElements()) {
                    fieldDefinition = (FieldDefinition)argumentEnum.nextElement();
                    outputWriter.write("call.addNamedArgument(\"");
                    outputWriter.write(fieldDefinition.getName());
                    outputWriter.write("\", \"");
                    outputWriter.write(this.getFieldName(fieldDefinition.getName()));
                    outputWriter.write("\");\n\t");
                }
                outputWriter.write("insertQuery.setCall(call);\n\tdescriptor.getQueryManager().setInsertQuery(insertQuery);\n\t");
                definition = (StoredProcedureDefinition)storedProcedureVector.elementAt(1);
                if (definition != null) {
                    outputWriter.write("\n\t//UPDATE QUERY\n");
                    outputWriter.write("\tUpdateObjectQuery updateQuery = new UpdateObjectQuery();\n\tcall = new StoredProcedureCall();\n");
                    outputWriter.write("\tcall.setProcedureName(\"");
                    outputWriter.write(definition.getName());
                    outputWriter.write("\");\n\t");
                    storedProcedureDefinitionArguments = definition.getArguments();
                    argumentEnum = storedProcedureDefinitionArguments.elements();
                    while (argumentEnum.hasMoreElements()) {
                        fieldDefinition = (FieldDefinition)argumentEnum.nextElement();
                        outputWriter.write("call.addNamedArgument(\"");
                        outputWriter.write(fieldDefinition.getName());
                        outputWriter.write("\", \"");
                        outputWriter.write(this.getFieldName(fieldDefinition.getName()));
                        outputWriter.write("\");\n\t");
                    }
                    outputWriter.write("updateQuery.setCall(call);\n\tdescriptor.getQueryManager().setUpdateQuery(updateQuery);\n");
                }
                definition = (StoredProcedureDefinition)storedProcedureVector.elementAt(2);
                outputWriter.write("\n\t//DELETE QUERY\n");
                outputWriter.write("\tDeleteObjectQuery deleteQuery = new DeleteObjectQuery();\n\tcall = new StoredProcedureCall();\n");
                outputWriter.write("\tcall.setProcedureName(\"");
                outputWriter.write(definition.getName());
                outputWriter.write("\");\n\t");
                storedProcedureDefinitionArguments = definition.getArguments();
                argumentEnum = storedProcedureDefinitionArguments.elements();
                while (argumentEnum.hasMoreElements()) {
                    fieldDefinition = (FieldDefinition)argumentEnum.nextElement();
                    outputWriter.write("call.addNamedArgument(\"");
                    outputWriter.write(fieldDefinition.getName());
                    outputWriter.write("\", \"");
                    outputWriter.write(this.getFieldName(fieldDefinition.getName()));
                    outputWriter.write("\");\n\t");
                }
                outputWriter.write("deleteQuery.setCall(call);\n\tdescriptor.getQueryManager().setDeleteQuery(deleteQuery);\n");
                if (storedProcedureVector.size() > 3) {
                    definition = (StoredProcedureDefinition)storedProcedureVector.elementAt(3);
                    outputWriter.write("\n\t//READ OBJECT QUERY\n");
                    outputWriter.write("\tReadObjectQuery readQuery = new ReadObjectQuery();\n\tcall = new StoredProcedureCall();\n");
                    outputWriter.write("\tcall.setProcedureName(\"");
                    outputWriter.write(definition.getName());
                    outputWriter.write("\");\n\t");
                    storedProcedureDefinitionArguments = definition.getArguments();
                    argumentEnum = storedProcedureDefinitionArguments.elements();
                    while (argumentEnum.hasMoreElements()) {
                        fieldDefinition = (FieldDefinition)argumentEnum.nextElement();
                        outputWriter.write("call.addNamedArgument(\"");
                        outputWriter.write(fieldDefinition.getName());
                        outputWriter.write("\", \"");
                        outputWriter.write(this.getFieldName(fieldDefinition.getName()));
                        outputWriter.write("\");\n\t");
                    }
                    outputWriter.write("readQuery.setCall(call);\n\tdescriptor.getQueryManager().setReadObjectQuery(readQuery);\n");
                }

                //generate read all stored procs.
                if (storedProcedureVector.size() > 4) {
                    definition = (StoredProcedureDefinition)storedProcedureVector.elementAt(4);
                    outputWriter.write("\n\t//READ ALL QUERY\n");
                    outputWriter.write("\tReadAllQuery readAllQuery = new ReadAllQuery();\n\tcall = new StoredProcedureCall();\n");
                    outputWriter.write("\tcall.setProcedureName(\"");
                    outputWriter.write(definition.getName());
                    outputWriter.write("\");\n\t");
                    storedProcedureDefinitionArguments = definition.getArguments();
                    argumentEnum = storedProcedureDefinitionArguments.elements();
                    while (argumentEnum.hasMoreElements()) {
                        fieldDefinition = (FieldDefinition)argumentEnum.nextElement();
                        outputWriter.write("call.addNamedArgument(\"");
                        outputWriter.write(fieldDefinition.getName());
                        outputWriter.write("\", \"");
                        outputWriter.write(this.getFieldName(fieldDefinition.getName()));
                        outputWriter.write("\");\n\t");
                    }
                    outputWriter.write("readAllQuery.setCall(call);\n\tdescriptor.getQueryManager().setReadAllQuery(readAllQuery);\n");
                }

                //generate 1:m mapping procedures.
                if (mappingHashtable != null) {
                    outputWriter.write("\n\t//MAPPING QUERIES\n");
                    //read all
                    outputWriter.write("\tReadAllQuery mappingQuery; \n");
                    outputWriter.write("\tDeleteAllQuery deleteMappingQuery; \n");
                    for (Enumeration e = mappingHashtable.keys(); e.hasMoreElements();) {
                        String mappingName = (String)e.nextElement();
                        definition = (StoredProcedureDefinition)((Hashtable)mappingHashtable.get(mappingName)).get("1MREAD");
                        if (definition != null) {
                            outputWriter.write("\n\t//MAPPING READALL QUERY FOR " + mappingName + "\n");
                            outputWriter.write("\tmappingQuery= new ReadAllQuery();\n\tcall = new StoredProcedureCall();\n");
                            outputWriter.write("\tcall.setProcedureName(\"");
                            outputWriter.write(definition.getName());
                            outputWriter.write("\");\n\t");
                            storedProcedureDefinitionArguments = definition.getArguments();
                            argumentEnum = storedProcedureDefinitionArguments.elements();
                            while (argumentEnum.hasMoreElements()) {
                                fieldDefinition = (FieldDefinition)argumentEnum.nextElement();
                                outputWriter.write("call.addNamedArgument(\"");
                                outputWriter.write(fieldDefinition.getName());
                                outputWriter.write("\", \"");
                                outputWriter.write(this.getFieldName(fieldDefinition.getName()));
                                outputWriter.write("\");\n\t");
                            }
                            outputWriter.write("mappingQuery.setCall(call);\n\t((OneToManyMapping)descriptor.getMappingForAttributeName(\"" + mappingName + "\")).setCustomSelectionQuery(mappingQuery);\n");
                        }

                        //DeleteAll Query
                        definition = (StoredProcedureDefinition)((Hashtable)mappingHashtable.get(mappingName)).get("1MDALL");
                        if (definition != null) {
                            outputWriter.write("\n\t//MAPPING DELETEALL QUERY FOR " + mappingName + "\n");
                            outputWriter.write("\tdeleteMappingQuery= new DeleteAllQuery();\n\tcall = new StoredProcedureCall();\n");
                            outputWriter.write("\tcall.setProcedureName(\"");
                            outputWriter.write(definition.getName());
                            outputWriter.write("\");\n\t");
                            storedProcedureDefinitionArguments = definition.getArguments();
                            argumentEnum = storedProcedureDefinitionArguments.elements();
                            while (argumentEnum.hasMoreElements()) {
                                fieldDefinition = (FieldDefinition)argumentEnum.nextElement();
                                outputWriter.write("call.addNamedArgument(\"");
                                outputWriter.write(fieldDefinition.getName());
                                outputWriter.write("\", \"");
                                outputWriter.write(this.getFieldName(fieldDefinition.getName()));
                                outputWriter.write("\");\n\t");
                            }
                            outputWriter.write("deleteMappingQuery.setCall(call);\n\t((OneToManyMapping)descriptor.getMappingForAttributeName(\"" + mappingName + "\")).setCustomDeleteAllQuery(deleteMappingQuery);\n");
                        }
                    }
                }
                outputWriter.write("}\n");
            }
            definition = (StoredProcedureDefinition)sequenceProcedures.get("SELECT");
            if (definition != null) {
                outputWriter.write("\n\tValueReadQuery seqSelectQuery = new ValueReadQuery();\n\tcall = new StoredProcedureCall();\n");
                outputWriter.write("\tcall.setProcedureName(\"");
                outputWriter.write(definition.getName());
                outputWriter.write("\");\n\t");
                storedProcedureDefinitionArguments = definition.getArguments();
                argumentEnum = storedProcedureDefinitionArguments.elements();
                while (argumentEnum.hasMoreElements()) {
                    fieldDefinition = (FieldDefinition)argumentEnum.nextElement();
                    outputWriter.write("call.addNamedArgument(\"");
                    outputWriter.write(fieldDefinition.getName());
                    outputWriter.write("\", \"");
                    outputWriter.write(this.getFieldName(fieldDefinition.getName()));
                    outputWriter.write("\");\n\t");
                    outputWriter.write("seqSelectQuery.addArgument(\"" + this.getFieldName(fieldDefinition.getName()));
                    outputWriter.write("\");\n\t");
                }
                outputWriter.write("seqSelectQuery.setCall(call);\n\tproject.getLogin().setSelectSequenceNumberQuery(seqSelectQuery);\n");
            }
            outputWriter.write("}\n");

            outputWriter.write(methodComment);
            outputWriter.write("\npublic static void amendDescriptors(org.eclipse.persistence.sessions.Project project) throws Exception{");
            outputWriter.write("\n\tamendSequences(project);");
            outputWriter.write("\n\tfor(Iterator enumtr = project.getDescriptors().values().iterator(); enumtr.hasNext();) {");
            outputWriter.write("\n\t\tDescriptor descriptor = (ClassDescriptor)enumtr.next();");
            outputWriter.write("\n\t\tif(!(descriptor.isAggregateDescriptor() || descriptor.isDescriptorForInterface())) {");
            outputWriter.write("\n\t\t\tMethod method = " + className + ".class.getMethod(\"amend\"+org.eclipse.persistence.internal.helper.Helper.getShortClassName(descriptor.getJavaClass())+\"ClassDescriptor\", new Class[] {ClassDescriptor.class});");
            outputWriter.write("\n\t\t\tmethod.invoke(null, new Object[] {descriptor});");
            outputWriter.write("\n\t\t}");
            outputWriter.write("\n\t}");
            outputWriter.write("\n}");
            outputWriter.write("\n}\n");
            outputWriter.flush();
        } catch (IOException exception) {
            throw ValidationException.fileError(exception);
        }
    }

    /**
     * INTERNAL: Generates the delete stored procedure for this descriptor
     */
    protected StoredProcedureDefinition generateDeleteStoredProcedure(ClassDescriptor descriptor) {
        DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
        deleteQuery.setDescriptor(descriptor);
        deleteQuery.setModifyRow(new DatabaseRecord());
        return this.generateObjectStoredProcedure(deleteQuery, descriptor.getPrimaryKeyFields(), "DEL_");

    }

    /**
     * INTERNAL: Generates the insert stored procedure for this descriptor
     */
    protected StoredProcedureDefinition generateInsertStoredProcedure(ClassDescriptor descriptor) {
        InsertObjectQuery insertQuery = new InsertObjectQuery();
        insertQuery.setDescriptor(descriptor);
        insertQuery.setModifyRow(descriptor.getObjectBuilder().buildTemplateInsertRow(getSession()));
        return this.generateObjectStoredProcedure(insertQuery, descriptor.getFields(), "INS_");
    }

    /**
     * INTERNAL: Generates the mapping stored procedures for this descriptor.
     * currently only 1:1 and 1:M are supported
     */
    protected Hashtable generateMappingStoredProcedures(ClassDescriptor descriptor) {
        Vector mappings = descriptor.getMappings();
        Hashtable mappingSP = new Hashtable();
        Hashtable mappingTable;
        for (Enumeration enumtr = mappings.elements(); enumtr.hasMoreElements();) {
            mappingTable = new Hashtable();
            DatabaseMapping mapping = (DatabaseMapping)enumtr.nextElement();
            if (mapping.isOneToManyMapping()) {
                if (!getSession().getPlatform().isOracle()) {
                    //reads not supported in oracle
                    mappingTable.put("1MREAD", generateOneToManyMappingReadProcedure((OneToManyMapping)mapping));
                }
                if (mapping.isPrivateOwned()) {
                    //generate delete all for 1:M query
                    mappingTable.put("1MDALL", generateOneToManyMappingDeleteAllProcedure((OneToManyMapping)mapping));
                }
                mappingSP.put(mapping.getAttributeName(), mappingTable);
            }
        }
        return mappingSP;

    }

    /**
     * INTERNAL: Generates the object level stored procedure based on the passed in query
     */
    protected StoredProcedureDefinition generateObjectStoredProcedure(DatabaseQuery query, List fields, String namePrefix) {
        String className = Helper.getShortClassName(query.getDescriptor().getJavaClass());

        return generateStoredProcedure(query, fields, getPrefix() + namePrefix + className);
    }

    /**
     * INTERNAL: Generates the delete all stored procedure for this mapping
     */
    protected StoredProcedureDefinition generateOneToManyMappingDeleteAllProcedure(OneToManyMapping mapping) {
        ClassDescriptor targetDescriptor = mapping.getReferenceDescriptor();
        DeleteAllQuery deleteAllQuery = new DeleteAllQuery();
        deleteAllQuery.setDescriptor(targetDescriptor);
        deleteAllQuery.setReferenceClass(targetDescriptor.getJavaClass());
        deleteAllQuery.setSelectionCriteria(mapping.getSelectionCriteria());
        return generateOneToManyMappingProcedures(mapping, deleteAllQuery, mapping.getTargetForeignKeyToSourceKeys(), "D_1M_");
    }

    /**
     * INTERNAL: Generates all the stored procedures for this mapping
     */
    protected StoredProcedureDefinition generateOneToManyMappingProcedures(OneToManyMapping mapping, DatabaseQuery query, Map fields, String namePrefix) {
        String sourceClassName = Helper.getShortClassName(mapping.getDescriptor().getJavaClass());
        return generateStoredProcedure(query, new ArrayList(fields.values()), getPrefix() + namePrefix + sourceClassName + "_" + mapping.getAttributeName());
    }

    /**
     * INTERNAL: Generates the read all stored procedure for this mapping
     */
    protected StoredProcedureDefinition generateOneToManyMappingReadProcedure(OneToManyMapping mapping) {
        ClassDescriptor targetDescriptor = mapping.getReferenceDescriptor();

        ReadAllQuery readAllQuery = new ReadAllQuery();
        readAllQuery.setDescriptor(targetDescriptor);
        readAllQuery.setReferenceClass(targetDescriptor.getJavaClass());
        readAllQuery.setSelectionCriteria(mapping.getSelectionCriteria());
        return generateOneToManyMappingProcedures(mapping, readAllQuery, mapping.getTargetForeignKeyToSourceKeys(), "R_1M_");
    }

    /**
     * INTERNAL: Generates the read all stored procedure for this descriptor
     */
    protected StoredProcedureDefinition generateReadAllStoredProcedure(ClassDescriptor descriptor) {
        ReadAllQuery readAllQuery = new ReadAllQuery();
        readAllQuery.setDescriptor(descriptor);
        readAllQuery.setReferenceClass(descriptor.getJavaClass());
        return generateObjectStoredProcedure(readAllQuery, descriptor.getPrimaryKeyFields(), "RALL_");

    }

    /**
     * INTERNAL: Generates the read stored procedure for this descriptor
     */
    protected StoredProcedureDefinition generateReadStoredProcedure(ClassDescriptor descriptor) {
        ReadObjectQuery readQuery = new ReadObjectQuery();
        readQuery.setDescriptor(descriptor);
        readQuery.setReferenceClass(descriptor.getJavaClass());
        return generateObjectStoredProcedure(readQuery, descriptor.getPrimaryKeyFields(), "READ_");

    }

    /**
     * INTERNAL: Generates the select and update stored procedures for this project.
     * no procedures are generated for native sequencing.  Note: reads are not supported in Oracle.
     */
    protected void generateSequenceStoredProcedures(org.eclipse.persistence.sessions.Project project) {
        DatabaseLogin login = (DatabaseLogin)project.getDatasourceLogin();
        if (login.shouldUseNativeSequencing()) {
            // There is nothing required for native SQL.
            return;
        }

        if (project.usesSequencing()) {
            if (!getSession().getPlatform().isOracle()) {
                // CR#3934352, updated to support new sequencing and use a single procedure.
                StoredProcedureDefinition definition = new StoredProcedureDefinition();
                definition.setName(Helper.truncate(project.getName() + "SEQ_SEL", MAX_NAME_SIZE));
                definition.addArgument("SEQ_NAME", String.class, 100);
                definition.addArgument("PREALLOC_SIZE", java.math.BigDecimal.class, 10);
                definition.addStatement("UPDATE " + ((TableSequence)login.getDefaultSequence()).getTableName() + " SET "
                    + ((TableSequence)login.getDefaultSequence()).getCounterFieldName() + " = "
                    + ((TableSequence)login.getDefaultSequence()).getCounterFieldName() + " + "
                    + getSession().getPlatform().getStoredProcedureParameterPrefix() + "PREALLOC_SIZE WHERE "
                    + ((TableSequence)login.getDefaultSequence()).getNameFieldName() + " = "
                    + getSession().getPlatform().getStoredProcedureParameterPrefix() + "SEQ_NAME");
                definition.addStatement("SELECT " + ((TableSequence)login.getDefaultSequence()).getCounterFieldName() + " FROM "
                    + ((TableSequence)login.getDefaultSequence()).getTableName() + " WHERE "
                    + ((TableSequence)login.getDefaultSequence()).getNameFieldName() + " = "
                    + getSession().getPlatform().getStoredProcedureParameterPrefix() + "SEQ_NAME");
                sequenceProcedures.put("SELECT", definition);
                writeDefinition(definition);
            }
        }
    }

    /**
     * INTERNAL: Generates the stored procedure for this query.  A new row
     * will be used for the check prepare.
     */
    protected StoredProcedureDefinition generateStoredProcedure(DatabaseQuery query, List fields, String name) {
        return generateStoredProcedure(query, fields, new DatabaseRecord(), name);
    }

    /**
     * INTERNAL: Generates the stored procedure for this query using the row
     * passed in for the check prepare.
     */
    protected StoredProcedureDefinition generateStoredProcedure(DatabaseQuery query, List fields, AbstractRecord rowForPrepare, String name) {
        StoredProcedureDefinition definition = new StoredProcedureDefinition();
        Vector callVector;
        Vector statementVector = new Vector();

        query.checkPrepare(getSession(), rowForPrepare, true);
        callVector = ((CallQueryMechanism)query.getQueryMechanism()).getCalls();
        if (callVector.isEmpty()) {
            if (((CallQueryMechanism)query.getQueryMechanism()).getCall() == null) {
                // do nothing
            } else {
                callVector.addElement(((CallQueryMechanism)query.getQueryMechanism()).getCall());
            }
        }
        Enumeration enumtr = callVector.elements();
        while (enumtr.hasMoreElements()) {
            SQLCall call = (SQLCall)enumtr.nextElement();
            statementVector.addElement(this.buildProcedureString(call));
        }
        definition.setStatements(statementVector);
        DatabaseField databaseField;
        AbstractRecord dataRow;
        Hashtable fieldNames = new Hashtable();
        List primaryKeyFields = fields;
        for (int index = 0; index < primaryKeyFields.size(); index++) {
            databaseField = (DatabaseField)primaryKeyFields.get(index);
            fieldNames.put(databaseField.getName(), this.schemaManager.getColumnInfo(null, null, databaseField.getTableName(), databaseField.getName()).firstElement());
        }

        definition.setName(Helper.truncate(name, MAX_NAME_SIZE));
        Enumeration fieldsEnum = fieldNames.keys();
        String prefixArgToken;
        if (getSession().getPlatform().isOracle()) {
            prefixArgToken = getSession().getPlatform().getStoredProcedureParameterPrefix();
        } else {
            prefixArgToken = "";
        }
        while (fieldsEnum.hasMoreElements()) {
            Number dataType;
            dataRow = (AbstractRecord)fieldNames.get(fieldsEnum.nextElement());
            dataType = (Number)dataRow.get("DATA_TYPE");
            Class type = this.getFieldType(dataType);
            String typeName = (String)dataRow.get("TYPE_NAME");
            if ((type != null) || (typeName == null) || (typeName.length() == 0)) {
                definition.addArgument(prefixArgToken + (String)dataRow.get("COLUMN_NAME"), type, ((Number)dataRow.get("COLUMN_SIZE")).intValue());
            } else {
                definition.addArgument(prefixArgToken + (String)dataRow.get("COLUMN_NAME"), typeName);
            }
        }

        return definition;

    }

    /**
     * PUBLIC:
     * generates all the stored procedures using the schema manager.  The schema manager
     * may be set to write directly to the database on the a file.  See
     * outputDDLToWriter(Writer) and outputDDLToDatabase() on SchemaManager
     */
    public void generateStoredProcedures() {
        // Must turn binding off to ensure literals are printed correctly.
        boolean wasBinding = getSession().getLogin().shouldBindAllParameters();
        getSession().getLogin().setShouldBindAllParameters(false);
        Map descriptors = getSession().getProject().getDescriptors();
        Iterator iterator = descriptors.keySet().iterator();
        ClassDescriptor desc;
        StoredProcedureDefinition definition;
        Vector definitionVector;
        this.generateSequenceStoredProcedures(getSession().getProject());
        while (iterator.hasNext()) {
            desc = (ClassDescriptor)descriptors.get(iterator.next());
            if (desc.isDescriptorForInterface() || desc.isDescriptorTypeAggregate()) {
                continue;
            }
            definition = this.generateInsertStoredProcedure(desc);
            definitionVector = new Vector();
            definitionVector.addElement(definition);
            this.writeDefinition(definition);
            definition = this.generateUpdateStoredProcedure(desc);
            definitionVector.addElement(definition);
            this.writeDefinition(definition);
            definition = this.generateDeleteStoredProcedure(desc);
            definitionVector.addElement(definition);
            this.writeDefinition(definition);
            if (!getSession().getPlatform().isOracle()) {
                definition = this.generateReadStoredProcedure(desc);
                definitionVector.addElement(definition);
                this.writeDefinition(definition);
                definition = this.generateReadAllStoredProcedure(desc);
                definitionVector.addElement(definition);
                this.writeDefinition(definition);
            }
            Hashtable mappingDefinitions = this.generateMappingStoredProcedures(desc);
            for (Enumeration enum2 = mappingDefinitions.elements(); enum2.hasMoreElements();) {
                Hashtable table = (Hashtable)enum2.nextElement();
                definition = (StoredProcedureDefinition)table.get("1MREAD");
                if (definition != null) {
                    this.writeDefinition(definition);
                }
                definition = (StoredProcedureDefinition)table.get("1MDALL");
                if (definition != null) {
                    this.writeDefinition(definition);
                }
            }
            this.storedProcedures.put(desc, definitionVector);
            if (!mappingDefinitions.isEmpty()) {
                this.mappingStoredProcedures.put(desc, mappingDefinitions);
            }
        }
        getSession().getLogin().setShouldBindAllParameters(wasBinding);
    }

    /**
     * PUBLIC:
     * generates all the stored procedures to the writer using
     * the schema manager outputDDLToWriter(Writer).
     */
    public void generateStoredProcedures(Writer writerOrNull) {
        this.writer = writerOrNull;

        this.schemaManager.outputDDLToWriter(getWriter());
        this.generateStoredProcedures();
        try {
            getWriter().flush();
        } catch (IOException exception) {
            System.out.println(exception);
            exception.printStackTrace();
        }
    }

    /**
     * INTERNAL: Generates the update stored procedure for this descriptor
     */
    protected StoredProcedureDefinition generateUpdateStoredProcedure(ClassDescriptor descriptor) {
        UpdateObjectQuery updateQuery = new UpdateObjectQuery();
        updateQuery.setDescriptor(descriptor);
        updateQuery.setModifyRow(descriptor.getObjectBuilder().buildTemplateUpdateRow(getSession()));

        return this.generateObjectStoredProcedure(updateQuery, descriptor.getFields(), "UPD_");

    }

    /**
     * INTERNAL:
     * return the original field name based on the argument name.
     */
    protected String getFieldName(String argumentName) {
        if (getSession().getPlatform().isOracle()) {
            return argumentName.substring(getSession().getPlatform().getStoredProcedureParameterPrefix().length());
        } else {
            return argumentName;
        }
    }

    /**
     * INTERNAL:
     * return the class corresponding to the passed in JDBC type.
     */
    protected Class getFieldType(Object jdbcDataType) {
        Integer key = Integer.valueOf(((Number)jdbcDataType).intValue());
        return (Class)intToTypeConverterHash.get(key);
    }

    public String getPrefix() {
        return prefix;
    }

    public AbstractSession getSession() {
        return schemaManager.getSession();
    }

    public Writer getWriter() {
        return writer;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    /**
     * INTERNAL:
     * Verify that this project and descriptors do not have optimistic locking.
     */
    protected void verify() throws org.eclipse.persistence.exceptions.ValidationException {
        if (getSession().getProject().usesOptimisticLocking()) {
            throw org.eclipse.persistence.exceptions.ValidationException.optimisticLockingNotSupportedWithStoredProcedureGeneration();
        }
    }

    public void writeDefinition(StoredProcedureDefinition definition) {
        this.schemaManager.replaceObject(definition);
    }
}
