class MsoVnfAdapterImpl {

    public void createVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vnfType,
            String vnfVersion, String genericVnfName, String vnfName, String vfModuleId, String requestType,
            String volumeGroupHeatStackId, String baseVfHeatStackId, String modelCustomizationUuid,
            Map<String, Object> inputs, Boolean failIfExists, Boolean backout, Boolean enableBridge,
            MsoRequest msoRequest, Holder<String> vnfId, Holder<Map<String, String>> outputs,
            Holder<VnfRollback> rollback) throws VnfException {
        boolean pollForCompletion = false;
        String vfModuleName = vnfName;
        String vfModuleType = vnfType;
        String vfVersion = vnfVersion;
        String mcu = modelCustomizationUuid;
        boolean useMCUuid = false;
        if (mcu != null && !mcu.isEmpty()) {
            if ("null".equalsIgnoreCase(mcu)) {
                logger.debug("modelCustomizationUuid: passed in as the string 'null' - will ignore: "
                        + modelCustomizationUuid);
                useMCUuid = false;
                mcu = "";
            } else {
                logger.debug("Found modelCustomizationUuid! Will use that: {}", mcu);
                useMCUuid = true;
            }
        }

        String requestTypeString = "";
        if (requestType != null && !"".equals(requestType)) {
            requestTypeString = requestType;
        }
        String nestedStackId = null;
        if (volumeGroupHeatStackId != null && !"".equals(volumeGroupHeatStackId)
                && !"null".equalsIgnoreCase(volumeGroupHeatStackId)) {
            nestedStackId = volumeGroupHeatStackId;
        }
        String nestedBaseStackId = null;
        if (baseVfHeatStackId != null && !"".equals(baseVfHeatStackId) && !"null".equalsIgnoreCase(baseVfHeatStackId)) {
            nestedBaseStackId = baseVfHeatStackId;
        }

        // This method will also handle doing things the "old" way - i.e., just orchestrate a VNF
        boolean oldWay = false;
        if (requestTypeString.startsWith("X")) {
            oldWay = true;
            logger.debug("orchestrating a VNF - *NOT* a module!");
            requestTypeString = requestTypeString.substring(1);
        }

        // let's parse out the request type we're being sent
        boolean isBaseRequest = false;
        boolean isVolumeRequest = false;
        if (requestTypeString.startsWith("VOLUME")) {
            isVolumeRequest = true;
        }

        logger.debug("requestTypeString = " + requestTypeString + ", nestedStackId = " + nestedStackId
                + ", nestedBaseStackId = " + nestedBaseStackId);

        // Build a default rollback object (no actions performed)
        VnfRollback vfRollback = new VnfRollback();
        vfRollback.setCloudSiteId(cloudSiteId);
        vfRollback.setCloudOwner(cloudOwner);
        vfRollback.setTenantId(tenantId);
        vfRollback.setMsoRequest(msoRequest);
        vfRollback.setRequestType(requestTypeString);
        vfRollback.setVolumeGroupHeatStackId(volumeGroupHeatStackId);
        vfRollback.setBaseGroupHeatStackId(baseVfHeatStackId);
        vfRollback.setIsBase(isBaseRequest);
        vfRollback.setModelCustomizationUuid(mcu);

        // handle a nestedStackId if sent- this one would be for the volume - so applies to both Vf and Vnf
        StackInfo nestedHeatStack = null;
        Map<String, Object> nestedVolumeOutputs = null;
        if (nestedStackId != null) {
            try {
                logger.debug("Querying for nestedStackId = {}", nestedStackId);
                nestedHeatStack = msoHeatUtils.queryStack(cloudSiteId, cloudOwner, tenantId, nestedStackId);
            } catch (MsoException me) {
                // Failed to query the Stack due to an openstack exception.
                // Convert to a generic VnfException
                me.addContext(CREATE_VFM_MODULE);
                String error = "Create VFModule: Attached heatStack ID Query " + nestedStackId + " in " + cloudOwner
                        + "/" + cloudSiteId + "/" + tenantId + ": " + me;
                logger.error(LoggingAnchor.NINE, MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleName, cloudOwner,
                        cloudSiteId, tenantId, OPENSTACK, QUERY_STACK, ErrorCode.BusinessProcessError.getValue(),
                        "MsoException trying to query nested stack", me);
                logger.debug("ERROR trying to query nested stack= {}", error);
                throw new VnfException(me);
            }
            if (nestedHeatStack == null || nestedHeatStack.getStatus() == HeatStatus.NOTFOUND) {
                String error = "Create VFModule: Attached heatStack ID DOES NOT EXIST " + nestedStackId + " in "
                        + cloudOwner + "/" + cloudSiteId + "/" + tenantId + " " + USER_ERROR;
                logger.error(LoggingAnchor.TEN, MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleName, cloudOwner,
                        cloudSiteId, tenantId, error, OPENSTACK, QUERY_STACK, ErrorCode.BusinessProcessError.getValue(),
                        "Create VFModule: Attached heatStack ID " + "DOES NOT EXIST");
                logger.debug(error);
                throw new VnfException(error, MsoExceptionCategory.USERDATA);
            } else {
                logger.debug("Found nested volume heat stack - copying values to inputs *later*");
                nestedVolumeOutputs = nestedHeatStack.getOutputs();
            }
        }

        // handle a nestedBaseStackId if sent- this is the stack ID of the base. Should be null for VNF requests
        StackInfo nestedBaseHeatStack = null;
        Map<String, Object> baseStackOutputs = null;
        if (nestedBaseStackId != null) {
            try {
                logger.debug("Querying for nestedBaseStackId = {}", nestedBaseStackId);
                nestedBaseHeatStack = msoHeatUtils.queryStack(cloudSiteId, cloudOwner, tenantId, nestedBaseStackId);
            } catch (MsoException me) {
                // Failed to query the Stack due to an openstack exception.
                // Convert to a generic VnfException
                me.addContext(CREATE_VFM_MODULE);
                String error = "Create VFModule: Attached baseHeatStack ID Query " + nestedBaseStackId + " in "
                        + cloudOwner + "/" + cloudSiteId + "/" + tenantId + ": " + me;
                logger.error(LoggingAnchor.NINE, MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleName, cloudOwner,
                        cloudSiteId, tenantId, OPENSTACK, QUERY_STACK, ErrorCode.BusinessProcessError.getValue(),
                        "MsoException trying to query nested base stack", me);
                logger.debug("ERROR trying to query nested base stack= {}", error);
                throw new VnfException(me);
            }
            if (nestedBaseHeatStack == null || nestedBaseHeatStack.getStatus() == HeatStatus.NOTFOUND) {
                String error = "Create VFModule: Attached base heatStack ID DOES NOT EXIST " + nestedBaseStackId
                        + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId + " " + USER_ERROR;
                logger.error(LoggingAnchor.TEN, MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleName, cloudOwner,
                        cloudSiteId, tenantId, error, OPENSTACK, QUERY_STACK, ErrorCode.BusinessProcessError.getValue(),
                        "Create VFModule: Attached base heatStack ID DOES NOT EXIST");
                logger.debug("Exception occurred", error);
                throw new VnfException(error, MsoExceptionCategory.USERDATA);
            } else {
                logger.debug("Found nested base heat stack - these values will be copied to inputs *later*");
                baseStackOutputs = nestedBaseHeatStack.getOutputs();
            }
        }

        try {
            VfModule vf = null;
            VnfResource vnfResource = null;
            VfModuleCustomization vfmc = null;
            if (useMCUuid) {
                vfmc = vfModuleCustomRepo.findFirstByModelCustomizationUUIDOrderByCreatedDesc(mcu);
                if (vfmc != null)
                    vf = vfmc.getVfModule();
                else
                    vf = null;

                // this will be the new way going forward. We find the vf by mcu - otherwise, code is the same.
                if (vf == null) {
                    logger.debug("Unable to find vfModuleCust with modelCustomizationUuid={}", mcu);
                    String error =
                            "Create vfModule error: Unable to find vfModuleCust with modelCustomizationUuid=" + mcu;
                    logger.error(LoggingAnchor.SIX, MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(),
                            "VF Module ModelCustomizationUuid", modelCustomizationUuid, OPENSTACK,
                            ErrorCode.DataError.getValue(),
                            "Create VF Module: Unable to find vfModule with " + "modelCustomizationUuid=" + mcu);
                    logger.debug(error);
                    throw new VnfException(error, MsoExceptionCategory.USERDATA);
                } else {
                    logger.trace("Found vfModuleCust entry {}", vfmc.toString());
                }
                if (vf.getIsBase()) {
                    isBaseRequest = true;
                    logger.debug("This is a BASE VF request!");
                } else {
                    logger.debug("This is *not* a BASE VF request!");
                    if (!isVolumeRequest && nestedBaseStackId == null) {
                        logger.debug(
                                "DANGER WILL ROBINSON! This is unexpected - no nestedBaseStackId with this non-base request");
                    }
                }
            }

            else { // This is to support gamma only - get info from vnf_resource table
                if (vfVersion != null && !vfVersion.isEmpty()) {
                    vnfResource = vnfResourceRepo.findByModelNameAndModelVersion(vnfType, vnfVersion);
                } else {
                    vnfResource = vnfResourceRepo.findByModelName(vnfType);
                }
                if (vnfResource == null) {
                    String error = "Create VNF: Unknown VNF Type: " + vnfType;
                    logger.error(LoggingAnchor.SIX, MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "VNF Type", vnfType,
                            OPENSTACK, ErrorCode.DataError.getValue(), "Create VNF: Unknown VNF Type");
                    logger.debug(error);
                    throw new VnfException(error, MsoExceptionCategory.USERDATA);
                }
                logger.debug("Got VNF module definition from Catalog: {}", vnfResource.toString());
            }
            // By here - we have either a vf or vnfResource

            // Add version check
            // First - see if it's in the VnfResource record
            // if we have a vf Module - then we have to query to get the VnfResource record.
            if (!oldWay) {
                if (vf != null) {
                    vnfResource = vf.getVnfResources();
                }
                if (vnfResource == null) {
                    logger.debug("Unable to find vnfResource will not error for now...");
                }
            }
            String minVersionVnf = null;
            String maxVersionVnf = null;
            if (vnfResource != null) {
                try {
                    minVersionVnf = vnfResource.getAicVersionMin();
                    maxVersionVnf = vnfResource.getAicVersionMax();
                } catch (Exception e) {
                    logger.debug("Unable to pull min/max version for this VNF Resource entry", e);
                    minVersionVnf = null;
                    maxVersionVnf = null;
                }
                if (minVersionVnf != null && "".equals(minVersionVnf)) {
                    minVersionVnf = null;
                }
                if (maxVersionVnf != null && "".equals(maxVersionVnf)) {
                    maxVersionVnf = null;
                }
            }
            if (minVersionVnf != null && maxVersionVnf != null) {
                MavenLikeVersioning aicV = new MavenLikeVersioning();

                // double check
                if (this.cloudConfig != null) {
                    Optional<CloudSite> cloudSiteOpt = this.cloudConfig.getCloudSite(cloudSiteId);
                    if (cloudSiteOpt.isPresent()) {
                        aicV.setVersion(cloudSiteOpt.get().getCloudVersion());
                        // Add code to handle unexpected values in here
                        boolean moreThanMin = true;
                        boolean equalToMin = true;
                        boolean moreThanMax = true;
                        boolean equalToMax = true;
                        boolean doNotTest = false;
                        try {
                            moreThanMin = aicV.isMoreRecentThan(minVersionVnf);
                            equalToMin = aicV.isTheSameVersion(minVersionVnf);
                            moreThanMax = aicV.isMoreRecentThan(maxVersionVnf);
                            equalToMax = aicV.isTheSameVersion(maxVersionVnf);
                        } catch (Exception e) {
                            logger.debug(
                                    "An exception occurred while trying to test Cloud Version {} - will default to not check",
                                    e.getMessage(), e);
                            doNotTest = true;
                        }
                        if (!doNotTest) {
                            if ((moreThanMin || equalToMin) // aic >= min
                                    && (equalToMax || !(moreThanMax))) { // aic <= max
                                logger.debug("VNF Resource " + vnfResource.getModelName() + ", ModelUuid="
                                        + vnfResource.getModelUUID() + " " + VERSION_MIN + " =" + minVersionVnf + " "
                                        + VERSION_MAX + " :" + maxVersionVnf + " supported on Cloud: " + cloudSiteId
                                        + " with AIC_Version:" + cloudSiteOpt.get().getCloudVersion());
                            } else {
                                // ERROR
                                String error = "VNF Resource type: " + vnfResource.getModelName() + ", ModelUuid="
                                        + vnfResource.getModelUUID() + " " + VERSION_MIN + " =" + minVersionVnf + " "
                                        + VERSION_MAX + " :" + maxVersionVnf + " NOT supported on Cloud: " + cloudSiteId
                                        + " with AIC_Version:" + cloudSiteOpt.get().getCloudVersion();
                                logger.error(LoggingAnchor.FIVE, MessageEnum.RA_CONFIG_EXC.toString(), error, OPENSTACK,
                                        ErrorCode.BusinessProcessError.getValue(), "Exception - setVersion");
                                logger.debug(error);
                                throw new VnfException(error, MsoExceptionCategory.USERDATA);
                            }
                        } else {
                            logger.debug("bypassing testing Cloud version...");
                        }
                    } // let this error out downstream to avoid introducing uncertainty at this stage
                } else {
                    logger.debug("cloudConfig is NULL - cannot check cloud site version");
                }
            }

            // By the time we get here - heatTemplateId and heatEnvtId should be populated (or null)
            HeatTemplate heatTemplate = null;
            HeatEnvironment heatEnvironment = null;
            if (oldWay) {
                // This will handle old Gamma BrocadeVCE VNF
                heatTemplate = vnfResource.getHeatTemplates();
            } else {
                if (vf != null) {
                    if (isVolumeRequest) {
                        heatTemplate = vf.getVolumeHeatTemplate();
                        heatEnvironment = vfmc.getVolumeHeatEnv();
                    } else {
                        heatTemplate = vf.getModuleHeatTemplate();
                        heatEnvironment = vfmc.getHeatEnvironment();
                    }
                }
            }

            if (heatTemplate == null) {
                String error = "UpdateVF: No Heat Template ID defined in catalog database for " + vfModuleType
                        + ", modelCustomizationUuid=" + mcu + ", vfModuleUuid="
                        + (vf != null ? vf.getModelUUID() : "null") + ", vnfResourceModelUuid="
                        + vnfResource.getModelUUID() + ", reqType=" + requestTypeString;
                logger.error(LoggingAnchor.SIX, MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Template " + "ID",
                        vfModuleType, OPENSTACK, ErrorCode.DataError.getValue(), error);
                logger.debug(error);
                throw new VnfException(error, MsoExceptionCategory.INTERNAL);
            } else {
                logger.debug("Got HEAT Template from DB: {}", heatTemplate.getHeatTemplate());
            }

            if (oldWay) {
                // This will handle old Gamma BrocadeVCE VNF
                logger.debug("No environment parameter found for this Type " + vfModuleType);
            } else {
                if (heatEnvironment == null) {
                    String error = "Update VNF: undefined Heat Environment. VF=" + vfModuleType
                            + ", modelCustomizationUuid=" + mcu + ", vfModuleUuid="
                            + (vf != null ? vf.getModelUUID() : "null") + ", vnfResourceModelUuid="
                            + vnfResource.getModelUUID() + ", reqType=" + requestTypeString;
                    logger.error(LoggingAnchor.FIVE, MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(),
                            "Heat " + "Environment ID", OPENSTACK, ErrorCode.DataError.getValue(), error);
                    logger.debug(error);
                    throw new VnfException(error, MsoExceptionCategory.INTERNAL);
                } else {
                    logger.debug("Got Heat Environment from DB: {}", heatEnvironment.getEnvironment());
                }
            }

            logger.debug("In MsoVnfAdapterImpl, about to call db.getNestedTemplates avec templateId="
                    + heatTemplate.getArtifactUuid());


            List<HeatTemplate> nestedTemplates = heatTemplate.getChildTemplates();
            Map<String, Object> nestedTemplatesChecked = new HashMap<>();
            if (nestedTemplates != null && !nestedTemplates.isEmpty()) {
                // for debugging print them out
                logger.debug("Contents of nestedTemplates - to be added to files: on stack:");
                for (HeatTemplate entry : nestedTemplates) {
                    nestedTemplatesChecked.put(entry.getTemplateName(), entry.getTemplateBody());
                    logger.debug("Adding Nested Template", entry.getTemplateName());
                }
            } else {
                logger.debug("No nested templates found - nothing to do here");
                nestedTemplatesChecked = null;
            }

            // Also add the files: for any get_files associated with this vnf_resource_id
            // *if* there are any
            List<HeatFiles> heatFiles = null;

            Map<String, Object> heatFilesObjects = new HashMap<>();

            // Add ability to turn on adding get_files with volume requests (by property).
            boolean addGetFilesOnVolumeReq = false;
            try {
                String propertyString = this.environment.getProperty(MsoVnfAdapterImpl.ADD_GET_FILES_ON_VOLUME_REQ);
                if ("true".equalsIgnoreCase(propertyString) || "y".equalsIgnoreCase(propertyString)) {
                    addGetFilesOnVolumeReq = true;
                    logger.debug("AddGetFilesOnVolumeReq - setting to true! {}", propertyString);
                }
            } catch (Exception e) {
                logger.debug("An error occured trying to get property " + MsoVnfAdapterImpl.ADD_GET_FILES_ON_VOLUME_REQ
                        + " - default to false", e);
            }

            if (!isVolumeRequest || addGetFilesOnVolumeReq) {
                if (oldWay) {
                    logger.debug("In MsoVnfAdapterImpl createVfModule, this should not happen, no heat files!");
                } else {
                    // now use VF_MODULE_TO_HEAT_FILES table
                    logger.debug(
                            "In MsoVnfAdapterImpl createVfModule, about to call db.getHeatFilesForVfModule avec vfModuleId="
                                    + vf.getModelUUID());
                    heatFiles = vf.getHeatFiles();
                }
                if (heatFiles != null && !heatFiles.isEmpty()) {
                    // add these to stack - to be done in createStack
                    // here, we will map them to Map<String, Object> from
                    // Map<String, HeatFiles>
                    // this will match the nested templates format
                    logger.debug("Contents of heatFiles - to be added to files: on stack");

                    for (HeatFiles heatfile : heatFiles) {
                        logger.debug(heatfile.getFileName() + " -> " + heatfile.getFileBody());
                        heatFilesObjects.put(heatfile.getFileName(), heatfile.getFileBody());
                    }
                } else {
                    logger.debug("No heat files found -nothing to do here");
                    heatFilesObjects = null;
                }
            }

            // Check that required parameters have been supplied
            String missingParams = null;
            List<String> paramList = new ArrayList<>();

            // consult the PARAM_ALIAS field to see if we've been
            // supplied an alias. Only check if we don't find it initially.
            // don't flag missing parameters if there's an environment - because they might be there.
            // And also new - add parameter to turn off checking all together if we find we're blocking orders we
            // shouldn't
            boolean checkRequiredParameters = true;
            try {
                String propertyString = this.environment.getProperty(MsoVnfAdapterImpl.CHECK_REQD_PARAMS);
                if ("false".equalsIgnoreCase(propertyString) || "n".equalsIgnoreCase(propertyString)) {
                    checkRequiredParameters = false;
                    logger.debug("CheckRequiredParameters is FALSE. Will still check but then skip blocking..."
                            + MsoVnfAdapterImpl.CHECK_REQD_PARAMS);
                }
            } catch (Exception e) {
                // No problem - default is true
                logger.debug("An exception occured trying to get property {}", MsoVnfAdapterImpl.CHECK_REQD_PARAMS, e);
            }
            // Part 1: parse envt entries to see if reqd parameter is there (before used a simple grep
            // Part 2: only submit to openstack the parameters in the envt that are in the heat template
            // Note this also removes any comments
            MsoHeatEnvironmentEntry mhee = null;
            if (heatEnvironment != null && heatEnvironment.getEnvironment() != null
                    && heatEnvironment.getEnvironment().contains("parameters:")) {

                StringBuilder sb = new StringBuilder(heatEnvironment.getEnvironment());

                mhee = new MsoHeatEnvironmentEntry(sb);
                StringBuilder sb2 = new StringBuilder("\nHeat Template Parameters:\n");
                for (HeatTemplateParam parm : heatTemplate.getParameters()) {
                    sb2.append("\t" + parm.getParamName() + ", required=" + parm.isRequired());
                }
                if (!mhee.isValid()) {
                    sb2.append("Environment says it's not valid! " + mhee.getErrorString());
                } else {
                    sb2.append("\nEnvironment:");
                    sb2.append(mhee.toFullString());
                }
                logger.debug(sb2.toString());
            } else {
                logger.debug("NO ENVIRONMENT for this entry");
            }
            // all variables converted to their native object types
            Map<String, Object> goldenInputs = null;

            ArrayList<String> parameterNames = new ArrayList<>();
            HashMap<String, String> aliasToParam = new HashMap<>();
            StringBuilder sb = new StringBuilder("\nTemplate Parameters:\n");
            int cntr = 0;
            try {
                for (HeatTemplateParam htp : heatTemplate.getParameters()) {
                    sb.append("param[" + cntr++ + "]=" + htp.getParamName());
                    parameterNames.add(htp.getParamName());
                    if (htp.getParamAlias() != null && !"".equals(htp.getParamAlias())) {
                        aliasToParam.put(htp.getParamAlias(), htp.getParamName());
                        sb.append(" ** (alias=" + htp.getParamAlias() + ")");
                    }
                    sb.append("\n");
                }
                logger.debug(sb.toString());
            } catch (Exception e) {
                logger.debug("??An exception occurred trying to go through Parameter Names {}", e.getMessage(), e);
            }
            // Step 1 - convert what we got as inputs (Map<String, String>) to a
            // Map<String, Object> - where the object matches the param type identified in the template
            // This will also not copy over params that aren't identified in the template
            goldenInputs = msoHeatUtils.convertInputMap(inputs, heatTemplate);
            // Step 2 - now simply add the outputs as we received them - no need to convert to string
            logger.debug("Now add in the base stack outputs if applicable");
            msoHeatUtils.copyBaseOutputsToInputs(goldenInputs, baseStackOutputs, parameterNames, aliasToParam);
            // Step 3 - add the volume inputs if any
            logger.debug("Now add in the volume stack outputs if applicable");
            msoHeatUtils.copyBaseOutputsToInputs(goldenInputs, nestedVolumeOutputs, parameterNames, aliasToParam);

            for (HeatTemplateParam parm : heatTemplate.getParameters()) {
                logger.debug("Parameter:'" + parm.getParamName() + "', isRequired=" + parm.isRequired() + ", alias="
                        + parm.getParamAlias());

                if (parm.isRequired() && (goldenInputs == null || !goldenInputs.containsKey(parm.getParamName()))) {
                    // The check for an alias was moved to the method in MsoHeatUtils - when we converted the
                    // Map<String, String> to Map<String, Object>
                    logger.debug("**Parameter " + parm.getParamName() + " is required and not in the inputs...check "
                            + "environment");
                    if (mhee != null && mhee.containsParameter(parm.getParamName())) {
                        logger.debug("Required parameter {} appears to be in environment - do not count as missing",
                                parm.getParamName());
                    } else {
                        logger.debug("adding to missing parameters list: {}", parm.getParamName());
                        if (missingParams == null) {
                            missingParams = parm.getParamName();
                        } else {
                            missingParams += "," + parm.getParamName();
                        }
                    }
                }
                paramList.add(parm.getParamName());
            }
            if (missingParams != null) {
                if (checkRequiredParameters) {
                    // Problem - missing one or more required parameters
                    String error = "Create VFModule: Missing Required inputs: " + missingParams;
                    logger.error(LoggingAnchor.FIVE, MessageEnum.RA_MISSING_PARAM.toString(), missingParams, OPENSTACK,
                            ErrorCode.DataError.getValue(), "Create VFModule: Missing Required inputs");
                    logger.debug(error);
                    throw new VnfException(error, MsoExceptionCategory.USERDATA);
                } else {
                    logger.debug("found missing parameters - but checkRequiredParameters is false - will not block");
                }
            } else {
                logger.debug("No missing parameters found - ok to proceed");
            }
            // We can now remove the recreating of the ENV with only legit params - that check is done for us,
            // and it causes problems with json that has arrays
            String newEnvironmentString = null;
            if (mhee != null) {
                newEnvironmentString = mhee.getRawEntry().toString();
            }

            // "Fix" the template if it has CR/LF (getting this from Oracle)
            String template = heatTemplate.getHeatTemplate();
            template = template.replaceAll("\r\n", "\n");

            // Have the tenant. Now deploy the stack itself
            // Ignore MsoTenantNotFound and MsoStackAlreadyExists exceptions
            // because we already checked for those.

            StackInfo heatStack = null;
            try {
                if (backout == null) {
                    backout = true;
                }
                if (failIfExists == null) {
                    failIfExists = false;
                }
                if (msoHeatUtils != null) {
                    heatStack = msoHeatUtils.createStack(cloudSiteId, cloudOwner, tenantId, vfModuleName, null,
                            template, goldenInputs, pollForCompletion, heatTemplate.getTimeoutMinutes(),
                            newEnvironmentString, nestedTemplatesChecked, heatFilesObjects, backout.booleanValue(),
                            failIfExists);
                    String resource = VF_MODULE;
                    if (isVolumeRequest) {
                        resource = VOLUME_GROUP;
                    }
                    if (msoRequest.getRequestId() != null) {
                        msoHeatUtils.updateResourceStatus(msoRequest.getRequestId(),
                                heatStack.isOperationPerformed()
                                        ? String.format(RESOURCE_CREATED_STATUS_MESSAGE, resource)
                                        : String.format(RESOURCE_EXIST_STATUS_MESSAGE, resource, resource));
                    }
                } else {
                    throw new MsoHeatNotFoundException();
                }
            } catch (MsoException me) {
                me.addContext(CREATE_VFM_MODULE);
                logger.error("Error creating Stack", me);
                throw new VnfException(me);
            } catch (NullPointerException npe) {
                logger.error("Error creating Stack", npe);
                throw new VnfException("NullPointerException during heat.createStack");
            } catch (Exception e) {
                logger.error("Error creating Stack", e);
                throw new VnfException("Exception during heat.createStack! " + e.getMessage());
            }
            // Reach this point if createStack is successful.
            // Populate remaining rollback info and response parameters.
            vfRollback.setVnfId(heatStack.getCanonicalName());
            vfRollback.setVnfCreated(true);

            vnfId.value = heatStack.getCanonicalName();
            outputs.value = copyStringOutputs(heatStack.getOutputs());
            rollback.value = vfRollback;
            logger.debug("VF Module {} successfully created", vfModuleName);
        } catch (Exception e) {
            logger.debug("unhandled exception in create VF", e);
            throw new VnfException("Exception during create VF " + e.getMessage());
        }
    }

    public  MsoVnfAdapterImpl();

}