/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package controllers.catalog; import static com.emc.vipr.client.core.util.ResourceUtils.uri; import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.common.collect.ImmutableMap; import models.BreadCrumb; import org.apache.commons.lang.StringUtils; import play.Logger; import play.i18n.Messages; import play.mvc.Controller; import play.mvc.With; import util.AssetOptionUtils; import util.CatalogServiceUtils; import util.ExecutionWindowUtils; import util.MessagesUtils; import util.ServiceDescriptorUtils; import util.ServiceFormUtils; import util.ServiceFormUtils.AssetFieldDescriptor; import com.emc.vipr.model.catalog.AssetOption; import com.emc.vipr.model.catalog.CatalogServiceFieldRestRep; import com.emc.vipr.model.catalog.CatalogServiceRestRep; import com.emc.vipr.model.catalog.ExecutionWindowRestRep; import com.emc.vipr.model.catalog.ServiceFieldRestRep; import com.emc.vipr.model.catalog.ServiceDescriptorRestRep; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gson.Gson; import controllers.Common; import controllers.catalog.ServiceCatalog.CategoryDef; import controllers.tenant.TenantSelector; import controllers.util.Models; import static controllers.Common.angularRenderArgs; import static controllers.Common.copyRenderArgsToAngular; @With(Common.class) public class Services extends Controller { private static void addBreadCrumbToRenderArgs(CatalogServiceRestRep service) { List<BreadCrumb> breadcrumbs = ServiceCatalog.createBreadCrumbs(Models.currentAdminTenant(), service); renderArgs.put("breadcrumbs", breadcrumbs); String backUrl = request.params.get("return"); if (StringUtils.isBlank(backUrl)) { String path = ""; URI categoryId = service.getCatalogCategory().getId(); if (categoryId != null) { Map<String, CategoryDef> catalog = ServiceCatalog.getCatalog(Models.currentAdminTenant()); CategoryDef category = catalog.get(categoryId.toString()); path = (category != null) ? category.path : path; } backUrl = Common.reverseRoute(ServiceCatalog.class, "view") + "#" + path; } renderArgs.put("backUrl", backUrl); } /** * Builds a form for a particular service */ public static void showForm(String serviceId) { TenantSelector.addRenderArgs(); CatalogServiceRestRep service = CatalogServiceUtils.getCatalogService(uri(serviceId)); List<CatalogServiceFieldRestRep> serviceFields = service.getCatalogServiceFields(); // If serviceDescriptor is null render another template that spells out the problem for the user. ServiceDescriptorRestRep serviceDescriptor = service.getServiceDescriptor(); if (serviceDescriptor == null) { corruptedService(service); } Map<String, Object> fieldOptions = new HashMap<String, Object>(); // add the breadcrumb addBreadCrumbToRenderArgs(service); // Mark the service as recently used // RecentUtils.usedService(service); Map<String, AssetFieldDescriptor> assetFieldDescriptors = ServiceFormUtils .createAssetFieldDescriptors(serviceDescriptor); // Calculate default values for all fields Map<String, String> defaultValues = getDefaultValues(serviceDescriptor); // Calculate asset parameters for any fields that are overridden Map<String, String> overriddenValues = getOverriddenValues(service); Map<String, String> availableAssets = getAvailableAssets(assetFieldDescriptors, overriddenValues); // Load any Asset Options for root fields so they are rendered directly onto the form List<ServiceFieldRestRep> allFields = ServiceDescriptorUtils.getAllFieldList(serviceDescriptor.getItems()); for (ServiceFieldRestRep field : allFields) { if (field.isAsset()) { // Compute to see if we have all dependencies. We have all dependencies if fieldsWeDependOn is empty // or it only contains fields we have in our overridden values. AssetFieldDescriptor fieldDescriptor = assetFieldDescriptors.get(field.getName()); boolean hasAllDependencies = overriddenValues.keySet().containsAll(fieldDescriptor.fieldsWeDependOn); boolean isOverridden = overriddenValues.containsKey(field.getName()); if (hasAllDependencies && !isOverridden) { List<AssetOption> options = getAssetOptions(field, availableAssets); fieldOptions.put(field.getType() + "-options", options); // If a required field is missing any options, display a warning message if (options.isEmpty() && field.isRequired() && !field.getType().equalsIgnoreCase("field")) { flash.put("rawWarning", MessagesUtils.get("service.missingAssets", field.getLabel())); } } } } Gson gson = new Gson(); String defaultValuesJSON = gson.toJson(defaultValues); String assetFieldDescriptorsJSON = gson.toJson(assetFieldDescriptors); String overriddenValuesJSON = gson.toJson(overriddenValues); boolean showForm = true; // Display an error message and don't display the form if an execution window is required but none are defined if (Boolean.TRUE.equals(service.isExecutionWindowRequired()) && !hasExecutionWindows()) { flash.error(MessagesUtils.get("service.noExecutionWindows")); showForm = false; } renderArgs.data.putAll(new ImmutableMap.Builder<String, Object>() .put("service", service) .put("serviceFields", serviceFields) .put("serviceDescriptor", serviceDescriptor) .put("assetFieldDescriptorsJSON", assetFieldDescriptorsJSON) .put("defaultValuesJSON", defaultValuesJSON) .put("overriddenValuesJSON", overriddenValuesJSON) .put("showForm", new Boolean(showForm)) .build()); // Adding to request, as renderArgs can't be dynamically named request.current().args.putAll(fieldOptions); copyRenderArgsToAngular(); angularRenderArgs().putAll(fieldOptions); angularRenderArgs().putAll(ImmutableMap.of( "assetFieldDescriptors", assetFieldDescriptors, "defaultValues", defaultValues, "overriddenValues", overriddenValues )); render(); } public static void corruptedService(CatalogServiceRestRep service) { TenantSelector.addRenderArgs(); addBreadCrumbToRenderArgs(service); String backUrl = Common.reverseRoute(ServiceCatalog.class, "view"); renderArgs.put("backUrl", backUrl); renderArgs.put("serviceWarning", MessagesUtils.get("service.corrupted")); renderArgs.put("showForm", Boolean.FALSE); render(service); } /** * Gets the default field values for the given service. * * @param descriptor * the service descriptor. * @return the default field values. */ private static Map<String, String> getDefaultValues(ServiceDescriptorRestRep descriptor) { Map<String, String> defaultValues = Maps.newHashMap(); List<ServiceFieldRestRep> allFields = ServiceDescriptorUtils.getAllFieldList(descriptor.getItems()); for (ServiceFieldRestRep field : allFields) { if (flash.contains(field.getName())) { defaultValues.put(field.getName(), flash.get(field.getName())); } else { defaultValues.put(field.getName(), field.getInitialValue()); } } return defaultValues; } /** * Gets the overridden (locked) values for the catalog service. * * @param service * the service. * @return the overridden values. */ private static Map<String, String> getOverriddenValues(CatalogServiceRestRep service) { List<CatalogServiceFieldRestRep> serviceFields = service.getCatalogServiceFields(); Map<String, String> overriddenValues = Maps.newHashMap(); for (CatalogServiceFieldRestRep field : serviceFields) { boolean overridden = Boolean.TRUE.equals(field.getOverride()); boolean hasValue = field.getValue() != null; if (overridden && hasValue) { overriddenValues.put(field.getName(), field.getValue()); } } return overriddenValues; } /** * Gets the available assets from the overridden values. The available assets are mapped by type not name (like the * overridden values are). * * @param descriptors * the asset field descriptors. * @param overriddenValues * the overridden values. * @return the available assets mapped by type. */ private static Map<String, String> getAvailableAssets(Map<String, AssetFieldDescriptor> descriptors, Map<String, String> overriddenValues) { Map<String, String> availableAssets = Maps.newHashMap(); for (Map.Entry<String, AssetFieldDescriptor> entry : descriptors.entrySet()) { String name = entry.getKey(); AssetFieldDescriptor descriptor = entry.getValue(); boolean isOverridden = overriddenValues.containsKey(name); // Add any overridden asset value to the map by type if (isOverridden) { String value = overriddenValues.get(name); availableAssets.put(descriptor.assetType, value); } } return availableAssets; } /** * Gets the asset options for the specified field, given the available assets. This should only be called for asset * fields which have all dependencies satisfied. * * @param field * the field. * @param availableAssets * the available assets. * @return the list of asset options. */ private static List<AssetOption> getAssetOptions(ServiceFieldRestRep field, Map<String, String> availableAssets) { List<AssetOption> allOptions = Lists.newArrayList(); try { List<AssetOption> options = AssetOptionUtils.getAssetOptions(field.getAssetType(), availableAssets); allOptions.addAll(options); } catch (RuntimeException e) { addAssetError(field, availableAssets, e); } return allOptions; } private static void addAssetError(ServiceFieldRestRep field, Map<String, String> availableAssets, RuntimeException e) { Logger.error(e, "Could not retrieve asset options for %s (type: %s, available: %s)", field.getName(), field.getType(), availableAssets); String errorMessage = Messages.get("service.assetError", field.getLabel(), e.getMessage()); String assetError = StringUtils.trimToEmpty(flash.get("assetError")); if (StringUtils.isNotBlank(assetError)) { assetError += "\n"; } assetError += errorMessage; flash.put("assetError", assetError); } /** * Determines if there are any execution windows defined. * * @return true if there are execution windows. */ private static boolean hasExecutionWindows() { List<ExecutionWindowRestRep> executionWindows = ExecutionWindowUtils.getExecutionWindows(); return (executionWindows != null && !executionWindows.isEmpty()); } }