class MsoVnfAdapterImpl {

    public void updateVfModule(String cloudSiteId, String cloudOwner, String tenantId, String vnfType,
            String vnfVersion, String vnfName, String requestType, String volumeGroupHeatStackId,
            String baseVfHeatStackId, String vfModuleStackId, String modelCustomizationUuid, Map<String, Object> inputs,
            MsoRequest msoRequest, Holder<Map<String, String>> outputs, Holder<VnfRollback> rollback)
            throws VnfException {
        String vfModuleName = vnfName;
        String vfModuleType = vnfType;

        StringBuilder sbInit = new StringBuilder();
        sbInit.append("updateVfModule: \n");
        sbInit.append("cloudOwner=" + cloudOwner + "\n");
        sbInit.append("cloudSiteId=" + cloudSiteId + "\n");
        sbInit.append("tenantId=" + tenantId + "\n");
        sbInit.append("vnfType=" + vnfType + "\n");
        sbInit.append("vnfVersion=" + vnfVersion + "\n");
        sbInit.append("vnfName=" + vnfName + "\n");
        sbInit.append("requestType=" + requestType + "\n");
        sbInit.append("volumeGroupHeatStackId=" + volumeGroupHeatStackId + "\n");
        sbInit.append("baseVfHeatStackId=" + baseVfHeatStackId + "\n");
        sbInit.append("vfModuleStackId=" + vfModuleStackId + "\n");
        sbInit.append("modelCustomizationUuid=" + modelCustomizationUuid + "\n");
        logger.debug(sbInit.toString());

        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;
        }

        if (inputs == null) {
            // Create an empty set of inputs
            inputs = new HashMap<>();
            logger.debug("inputs == null - setting to empty");
        }

        boolean isBaseRequest = false;
        boolean isVolumeRequest = false;
        if (requestTypeString.startsWith("VOLUME")) {
            isVolumeRequest = true;
        }
        if ((vfModuleName == null || "".equals(vfModuleName.trim())) && vfModuleStackId != null) {
            vfModuleName = this.getVfModuleNameFromModuleStackId(vfModuleStackId);
        }

        logger.debug("Updating VFModule: " + vfModuleName + " of type " + vfModuleType + "in " + cloudOwner + "/"
                + cloudSiteId + "/" + tenantId);
        logger.debug("requestTypeString = " + requestTypeString + ", nestedVolumeStackId = " + 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.setVfModuleStackId(vfModuleStackId);
        vfRollback.setModelCustomizationUuid(mcu);

        StackInfo heatStack;
        logger.debug("UpdateVfModule - querying for {}", vfModuleName);
        try {
            heatStack = msoHeatUtils.queryStack(cloudSiteId, cloudOwner, tenantId, vfModuleName);
        } catch (MsoException me) {
            // Failed to query the Stack due to an openstack exception.
            // Convert to a generic VnfException
            me.addContext("UpdateVFModule");
            String error = "Update VFModule: Query " + vfModuleName + " in " + cloudOwner + "/" + cloudSiteId + "/"
                    + tenantId + ": " + me;
            logger.error(LoggingAnchor.NINE, MessageEnum.RA_QUERY_VNF_ERR.toString(), vfModuleName, cloudOwner,
                    cloudSiteId, tenantId, OPENSTACK, QUERY_STACK, ErrorCode.DataError.getValue(),
                    "Exception - " + QUERY_STACK, me);
            logger.debug(error);
            throw new VnfException(me);
        }

        // TODO - do we need to check for the other status possibilities?
        if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
            // Not Found
            String error = "Update VF: Stack " + vfModuleName + " does not exist in " + cloudOwner + "/" + cloudSiteId
                    + "/" + tenantId;
            logger.error(LoggingAnchor.NINE, MessageEnum.RA_VNF_NOT_EXIST.toString(), vfModuleName, cloudOwner,
                    cloudSiteId, tenantId, OPENSTACK, QUERY_STACK, ErrorCode.DataError.getValue(), error);
            throw new VnfNotFound(cloudSiteId, cloudOwner, tenantId, vfModuleName);
        } else {
            logger.debug("Found Existing stack, status={}", heatStack.getStatus());
            // Populate the outputs from the existing stack.
            outputs.value = copyStringOutputs(heatStack.getOutputs());
            rollback.value = vfRollback; // Default rollback - no updates performed
        }

        // 1604 Cinder Volume support - handle a nestedStackId if sent (volumeGroupHeatStackId):
        StackInfo nestedHeatStack = 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("UpdateVFModule");
                String error = "Update VF: Attached heatStack ID Query " + nestedStackId + " in " + cloudOwner + "/"
                        + cloudSiteId + "/" + tenantId + ": " + me;
                logger.error(LoggingAnchor.NINE, MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfName, cloudOwner,
                        cloudSiteId, tenantId, OPENSTACK, QUERY_STACK, ErrorCode.DataError.getValue(),
                        "Exception - " + error, me);
                logger.debug("ERROR trying to query nested stack= {}", error);
                throw new VnfException(me);
            }
            if (nestedHeatStack == null || nestedHeatStack.getStatus() == HeatStatus.NOTFOUND) {
                String error = "Update VFModule: Attached volume heatStack ID DOES NOT EXIST " + nestedStackId + " in "
                        + cloudOwner + "/" + cloudSiteId + "/" + tenantId + " " + USER_ERROR;
                logger.error(LoggingAnchor.TEN, MessageEnum.RA_QUERY_VNF_ERR.toString(), vnfName, cloudOwner,
                        cloudSiteId, tenantId, error, OPENSTACK, QUERY_STACK, ErrorCode.DataError.getValue(), error);
                logger.debug(error);
                throw new VnfException(error, MsoExceptionCategory.USERDATA);
            } else {
                logger.debug("Found nested heat stack - copying values to inputs *later*");
                msoHeatUtils.copyStringOutputsToInputs(inputs, nestedHeatStack.getOutputs(), false);
            }
        }
        // handle a nestedBaseStackId if sent - this is the stack ID of the base.
        StackInfo nestedBaseHeatStack = 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("UpdateVfModule");
                String error = "Update 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.DataError.getValue(),
                        "Exception - " + error, me);
                logger.debug("ERROR trying to query nested base stack= {}", error);
                throw new VnfException(me);
            }
            if (nestedBaseHeatStack == null || nestedBaseHeatStack.getStatus() == HeatStatus.NOTFOUND) {
                String error = "Update 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.DataError.getValue(), error);
                logger.debug(error);
                throw new VnfException(error, MsoExceptionCategory.USERDATA);
            } else {
                logger.debug("Found nested base heat stack - copying values to inputs *later*");
                msoHeatUtils.copyStringOutputsToInputs(inputs, nestedBaseHeatStack.getOutputs(), false);
            }
        }

        // Retrieve the VF definition
        VnfResource vnfResource = null;
        VfModule vf = null;
        VfModuleCustomization vfmc = null;
        if (useMCUuid) {
            vfmc = vfModuleCustomRepo.findFirstByModelCustomizationUUIDOrderByCreatedDesc(modelCustomizationUuid);
            vf = vfmc != null ? vfmc.getVfModule() : null;
            if (vf == null) {
                logger.debug("Unable to find a vfModule matching modelCustomizationUuid={}", mcu);
            }
        } else {
            logger.debug("1707 and later - MUST PROVIDE Model Customization UUID!");
        }
        if (vf == null) {
            String error = "Update VfModule: unable to find vfModule with modelCustomizationUuid=" + mcu;
            logger.error(LoggingAnchor.SIX, MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "VF Module Type", vfModuleType,
                    OPENSTACK, ErrorCode.DataError.getValue(), error);
            throw new VnfException(error, MsoExceptionCategory.USERDATA);
        }
        logger.debug("Got VF module definition from Catalog: {}", vf.toString());
        if (vf.getIsBase()) {
            isBaseRequest = true;
            logger.debug("This a BASE update request");
        } else {
            logger.debug("This is *not* a BASE VF update request");
            if (!isVolumeRequest && nestedBaseStackId == null) {
                logger.debug(
                        "DANGER WILL ROBINSON! This is unexpected - no nestedBaseStackId with this non-base request");
            }
        }

        // 1607 - 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 (vf.getModelUUID() != null) {
            String vnfResourceModelUuid = vf.getModelUUID();

            vnfResource = vf.getVnfResources();
            if (vnfResource == null) {
                logger.debug("Unable to find vnfResource at ? will not error for now...", vnfResourceModelUuid);
            }
        }

        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());
                    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 occured while trying to test AIC 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() + " " + VERSION_MIN + " ="
                                    + minVersionVnf + " " + VERSION_MAX + " :" + maxVersionVnf + " supported on Cloud: "
                                    + cloudSiteId + " with AIC_Version:" + aicV);
                        } else {
                            // ERROR
                            String error = "VNF Resource type: " + vnfResource.getModelName() + " " + VERSION_MIN + " ="
                                    + minVersionVnf + " " + VERSION_MAX + " :" + maxVersionVnf
                                    + " NOT supported on Cloud: " + cloudSiteId + " with AIC_Version:" + aicV;
                            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 AIC version...");
                    }
                } // let this error out downstream to avoid introducing uncertainty at this stage
            } else {
                logger.debug("cloudConfig is NULL - cannot check cloud site version");
            }

        } else {
            logger.debug("AIC Version not set in VNF_Resource - do not error for now - not checked.");
        }
        // End Version check 1607

        HeatTemplate heatTemplate = null;
        HeatEnvironment heatEnvironment = 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.getModelUUID() + ", reqType="
                    + requestTypeString;
            logger.error(LoggingAnchor.SIX, MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Template ID",
                    vfModuleType, OPENSTACK, ErrorCode.DataError.getValue(), error);
            throw new VnfException(error, MsoExceptionCategory.INTERNAL);
        } else {
            logger.debug("Got HEAT Template from DB: {}", heatTemplate.getHeatTemplate());
        }

        if (heatEnvironment == null) {
            String error = "Update VNF: undefined Heat Environment. VF=" + vfModuleType + ", modelCustomizationUuid="
                    + mcu + ", vfModuleUuid=" + vf.getModelUUID() + ", reqType=" + requestTypeString;
            logger.error(LoggingAnchor.FIVE, MessageEnum.RA_VNF_UNKNOWN_PARAM.toString(), "Heat Environment ID",
                    OPENSTACK, ErrorCode.DataError.getValue(), 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(entry.getTemplateName() + " -> " + entry.getTemplateBody());
            }
        } else {
            logger.debug("No nested templates found - nothing to do here");
            nestedTemplatesChecked = null;
        }

        // Also add the files: for any get_files associated with this VfModule
        // *if* there are any
        logger.debug("In MsoVnfAdapterImpl.updateVfModule, about to call db.getHeatFiles avec vfModuleId={}",
                vf.getModelUUID());

        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 {} - default to false",
                    MsoVnfAdapterImpl.ADD_GET_FILES_ON_VOLUME_REQ, e);
        }
        if (!isVolumeRequest || addGetFilesOnVolumeReq) {
            logger.debug("In MsoVnfAdapterImpl updateVfModule, 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<>();

        // New for 1510 - consult the PARAM_ALIAS field to see if we've been
        // supplied an alias. Only check if we don't find it initially.
        // Also new in 1510 - 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);
        }
        // 1604 - Add enhanced environment & parameter checking
        // 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().toLowerCase().contains("parameters:")) {
            logger.debug("Enhanced environment checking enabled - 1604");
            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");
        }
        // New for 1607 - support params of json type
        HashMap<String, JsonNode> jsonParams = new HashMap<>();
        boolean hasJson = false;

        for (HeatTemplateParam parm : heatTemplate.getParameters()) {
            logger.debug("Parameter:'" + parm.getParamName() + "', isRequired=" + parm.isRequired() + ", alias="
                    + parm.getParamAlias());
            // handle json
            String parameterType = parm.getParamType();
            if (parameterType == null || "".equals(parameterType.trim())) {
                parameterType = "String";
            }
            JsonNode jsonNode = null;
            if ("json".equalsIgnoreCase(parameterType) && inputs != null) {
                if (inputs.containsKey(parm.getParamName())) {
                    hasJson = true;
                    String jsonString = null;
                    try {
                        jsonString = JSON_MAPPER.writeValueAsString(inputs.get(parm.getParamName()));
                        jsonNode = JSON_MAPPER.readTree(jsonString);
                    } catch (JsonParseException jpe) {
                        // TODO - what to do here?
                        // for now - send the error to debug
                        logger.debug("Json Error Converting {} - {}", parm.getParamName(), jpe.getMessage(), jpe);
                        hasJson = false;
                        jsonNode = null;
                    } catch (Exception e) {
                        // or here?
                        logger.debug("Json Error Converting {} {}", parm.getParamName(), e.getMessage(), e);
                        hasJson = false;
                        jsonNode = null;
                    }
                    if (jsonNode != null) {
                        jsonParams.put(parm.getParamName(), jsonNode);
                    }
                } else if (inputs.containsKey(parm.getParamAlias())) {
                    hasJson = true;
                    String jsonString = null;
                    try {
                        jsonString = (String) inputs.get(parm.getParamAlias());
                        jsonNode = JSON_MAPPER.readTree(jsonString);
                    } catch (JsonParseException jpe) {
                        // TODO - what to do here?
                        // for now - send the error to debug, but just leave it as a String
                        String errorMessage = jpe.getMessage();
                        logger.debug("Json Error Converting " + parm.getParamName() + " - " + errorMessage, jpe);
                        hasJson = false;
                        jsonNode = null;
                    } catch (Exception e) {
                        // or here?
                        logger.debug("Json Error Converting " + parm.getParamName() + " " + e.getMessage(), e);
                        hasJson = false;
                        jsonNode = null;
                    }
                    if (jsonNode != null) {
                        // Notice here - we add it to the jsonParams hashMap with the actual name -
                        // then manipulate the inputs so when we check for aliases below - it will not
                        // get flagged.
                        jsonParams.put(parm.getParamName(), jsonNode);
                        inputs.remove(parm.getParamAlias());
                        inputs.put(parm.getParamName(), jsonString);
                    }
                } // TODO add a check for the parameter in the env file
            }

            if (parm.isRequired() && (inputs == null || !inputs.containsKey(parm.getParamName()))) {
                if (inputs.containsKey(parm.getParamAlias())) {
                    // They've submitted using an alias name. Remove that from inputs, and add back using real name.
                    String realParamName = parm.getParamName();
                    String alias = parm.getParamAlias();
                    Object value = inputs.get(alias);
                    logger.debug("*Found an Alias: paramName=" + realParamName + ",alias=" + alias + ",value=" + value);
                    inputs.remove(alias);
                    inputs.put(realParamName, value);
                    logger.debug("{} entry removed from inputs, added back using {}", alias, realParamName);
                }
                // enhanced - check if it's in the Environment (note: that method
                else 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) {
            // Problem - missing one or more required parameters
            if (checkRequiredParameters) {
                String error = "Update VNF: Missing Required inputs: " + missingParams;
                logger.error(LoggingAnchor.FIVE, MessageEnum.RA_MISSING_PARAM.toString(), missingParams, OPENSTACK,
                        ErrorCode.DataError.getValue(), error);
                throw new VnfException(error, MsoExceptionCategory.USERDATA);
            } else {
                logger.debug("found missing parameters - but checkRequiredParameters is false - will not block");
            }
        }

        // Just submit the envt entry as received from the database
        String newEnvironmentString = null;
        if (mhee != null) {
            newEnvironmentString = mhee.getRawEntry().toString();
        }
        // Remove any extraneous parameters (don't throw an error)
        if (inputs != null) {
            List<String> extraParams = new ArrayList<>();
            extraParams.addAll(inputs.keySet());
            // This is not a valid parameter for this template
            extraParams.removeAll(paramList);
            if (!extraParams.isEmpty()) {
                logger.warn(LoggingAnchor.SIX, MessageEnum.RA_VNF_EXTRA_PARAM.toString(), vnfType,
                        extraParams.toString(), OPENSTACK, ErrorCode.DataError.getValue(), "Extra params");
                inputs.keySet().removeAll(extraParams);
            }
        }
        Map<String, Object> goldenInputs = copyStringInputs(inputs);
        // 1607 - when we get here - we have clean inputs. Create inputsTwo in case we have json
        Map<String, Object> inputsTwo = null;
        if (hasJson && jsonParams.size() > 0) {
            inputsTwo = new HashMap<>();
            for (Map.Entry<String, Object> entry : inputs.entrySet()) {
                String keyParamName = entry.getKey();
                Object value = entry.getValue();
                if (jsonParams.containsKey(keyParamName)) {
                    inputsTwo.put(keyParamName, jsonParams.get(keyParamName));
                } else {
                    inputsTwo.put(keyParamName, value);
                }
            }
            goldenInputs = inputsTwo;
        }

        // "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.
        try {
            heatStack = heatU.updateStack(cloudSiteId, cloudOwner, tenantId, vfModuleName, template, goldenInputs, true,
                    heatTemplate.getTimeoutMinutes(), newEnvironmentString,
                    // heatEnvironmentString,
                    nestedTemplatesChecked, heatFilesObjects);
        } catch (MsoException me) {
            me.addContext("UpdateVFModule");
            String error = "Update VFModule " + vfModuleType + " in " + cloudOwner + "/" + cloudSiteId + "/" + tenantId
                    + ": " + me;
            logger.error(LoggingAnchor.EIGHT, MessageEnum.RA_UPDATE_VNF_ERR.toString(), vfModuleType, cloudOwner,
                    cloudSiteId, tenantId, OPENSTACK, ErrorCode.DataError.getValue(), "Exception - " + error, me);
            throw new VnfException(me);
        }

        // Reach this point if updateStack is successful.
        // Populate remaining rollback info and response parameters.
        vfRollback.setVnfId(heatStack.getCanonicalName());
        vfRollback.setVnfCreated(true);
        outputs.value = copyStringOutputs(heatStack.getOutputs());
        rollback.value = vfRollback;
    }

    public  MsoVnfAdapterImpl();

}