/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package controllers.catalog; import static com.emc.vipr.client.core.util.ResourceUtils.uri; import static util.BourneUtil.getCatalogClient; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import models.ACLs; import models.BreadCrumb; import models.RoleAssignmentType; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.lang.StringUtils; import play.Logger; import play.Play; import play.data.validation.Check; import play.data.validation.CheckWith; import play.data.validation.MaxSize; import play.data.validation.Min; import play.data.validation.MinSize; import play.data.validation.Required; import play.data.validation.Validation; import play.exceptions.UnexpectedException; import play.i18n.Messages; import play.mvc.Catch; import play.mvc.Util; import play.mvc.With; import play.vfs.VirtualFile; import util.ACLUtils; import util.AssetOptionUtils; import util.CatalogCategoryUtils; import util.CatalogImageUtils; import util.CatalogServiceUtils; import util.EnumOption; import util.ExecutionWindowUtils; import util.MessagesUtils; import util.ServiceDescriptorUtils; import util.StringComparator; import util.descriptor.ServiceFieldValidator; import com.emc.storageos.model.DataObjectRestRep; import com.emc.vipr.client.exceptions.ViPRException; import com.emc.vipr.model.catalog.AssetOption; import com.emc.vipr.model.catalog.CatalogCategoryCommonParam; import com.emc.vipr.model.catalog.CatalogCategoryCreateParam; import com.emc.vipr.model.catalog.CatalogCategoryRestRep; import com.emc.vipr.model.catalog.CatalogCategoryUpdateParam; import com.emc.vipr.model.catalog.CatalogImageRestRep; import com.emc.vipr.model.catalog.CatalogServiceCommonParam; import com.emc.vipr.model.catalog.CatalogServiceCreateParam; import com.emc.vipr.model.catalog.CatalogServiceFieldParam; import com.emc.vipr.model.catalog.CatalogServiceFieldRestRep; import com.emc.vipr.model.catalog.CatalogServiceRestRep; import com.emc.vipr.model.catalog.CatalogServiceUpdateParam; import com.emc.vipr.model.catalog.ExecutionWindowRestRep; import com.emc.vipr.model.catalog.Option; import com.emc.vipr.model.catalog.ServiceDescriptorRestRep; import com.emc.vipr.model.catalog.ServiceFieldRestRep; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import controllers.Common; import controllers.deadbolt.Restrict; import controllers.deadbolt.Restrictions; import controllers.security.Security; import controllers.tenant.AclEntryForm; import controllers.tenant.TenantSelector; import controllers.util.AbstractRestRepForm; import controllers.util.Models; @With(Common.class) @Restrictions({ @Restrict("TENANT_ADMIN") }) public class EditCatalog extends ServiceCatalog { protected static final String CATEGORY_MODEL_NAME = "CatalogCategory"; protected static final String SERVICE_MODEL_NAME = "CatalogService"; protected static final String CREATE_SERVICE_KEY = "Service.create.title"; protected static final String CREATE_CATEGORY_KEY = "Category.create.title"; protected static final String EDIT_CATEGORY_KEY = "Category.edit.title"; public static void edit() { Map<String, CategoryDef> catalog = getCatalog(Models.currentAdminTenant()); Map<String, List<BreadCrumb>> breadcrumbs = createBreadCrumbs(catalog); boolean catalogUpdateAvailable = CatalogCategoryUtils.isUpdateAvailable(uri(Models.currentAdminTenant())); TenantSelector.addRenderArgs(); render(catalog, breadcrumbs, catalogUpdateAvailable); } /** * Gets the URL for accessing to a particular category using the base edit catalog URL with the category path as the * URL fragment. * * @param categoryId * the ID of the category. * @return the URL for accessing the category in the catalog. */ private static String getCategoryUrl(String tenantId, String categoryId) { Map<String, CategoryDef> catalog = getCatalog(tenantId); CategoryDef category = catalog.get(categoryId); String path = category != null ? category.path : ""; return Common.reverseRoute(EditCatalog.class, "edit") + "#" + path; } /** * Sends the user back to the catalog at the given category with a success message. * * @param categoryId * the ID of the category to show the user. * @param message * the success message. * @param args * the success message arguments. */ private static void catalogUpdated(String categoryId, String messageKey, Object... args) { String tenantId = Models.currentAdminTenant(); flash.success(Messages.get(messageKey, args)); catalogModified(tenantId); showCategory(tenantId, categoryId); } @Util private static void addACLsToRenderArgs() { renderArgs.put("acls", ACLs.options(ACLs.USE)); Set<EnumOption> aclTypes = new TreeSet<EnumOption>(); aclTypes.addAll(Arrays.asList(EnumOption.options(RoleAssignmentType.values(), "RoleAssignmentType"))); renderArgs.put("aclTypes", aclTypes); } private static void addImagesToRenderArgs() { renderArgs.put("images", loadImageOptions()); } private static List<Option> loadImageOptions() { VirtualFile imageDir = Play.getVirtualFile("public/img/serviceCatalog"); List<Option> images = Lists.newArrayList(); for (VirtualFile f : imageDir.list()) { String label = f.getName().replaceAll("(icon_|.png)", ""); images.add(new Option(f.getName(), label)); } for (CatalogImageRestRep image : CatalogImageUtils.getCatalogImages()) { String filename = StringUtils.substringBeforeLast(image.getName(), "."); images.add(new Option(image.getId().toString(), filename)); } Collections.sort(images, new BeanComparator("value", new StringComparator(false))); return images; } public static void imagesJson() { renderJSON(loadImageOptions()); } private static void addExecutionWindowsToRenderArgs() { // Query windows by current chosen tenant id. By default it's home tenant. List<ExecutionWindowRestRep> executionWindows = ExecutionWindowUtils.getExecutionWindows( URI.create(Models.currentAdminTenant()) ); renderArgs.put("executionWindows", executionWindows); } private static void addBaseServicesToRenderArgs() { Map<String, List<ServiceDescriptorRestRep>> descriptors = Maps.newTreeMap(); for (ServiceDescriptorRestRep descriptor : ServiceDescriptorUtils.getDescriptors()) { String category = StringUtils.defaultString(descriptor.getCategory()); List<ServiceDescriptorRestRep> values = descriptors.get(category); if (values == null) { values = Lists.newArrayList(); descriptors.put(category, values); } values.add(descriptor); } for (List<ServiceDescriptorRestRep> values : descriptors.values()) { Collections.sort(values, new ServiceDescriptorComparator()); } renderArgs.put("baseServices", descriptors); } private static void addBreadCrumbToRenderArgs(String tenantId, CategoryForm form) { Map<String, CategoryDef> catalog = getCatalog(tenantId); if (form.id != null) { List<BreadCrumb> breadcrumbs = createBreadCrumbs(form.id, catalog); addBreadCrumb(breadcrumbs, MessagesUtils.get(EDIT_CATEGORY_KEY)); renderArgs.put("breadcrumbs", breadcrumbs); } else { List<BreadCrumb> breadcrumbs = createBreadCrumbs(form.parentId, catalog); addBreadCrumb(breadcrumbs, MessagesUtils.get(CREATE_CATEGORY_KEY)); renderArgs.put("breadcrumbs", breadcrumbs); } addBackUrlToRenderArgs(Models.currentAdminTenant(), (form.id != null) ? form.id : form.parentId); } private static void addBreadCrumbToRenderArgs(String tenantId, ServiceForm form) { Map<String, CategoryDef> catalog = getCatalog(tenantId); List<BreadCrumb> breadcrumbs = createBreadCrumbs(form.owningCategoryId, catalog); if (form.id != null) { addBreadCrumb(breadcrumbs, form.title); } else { addBreadCrumb(breadcrumbs, MessagesUtils.get(CREATE_SERVICE_KEY)); } renderArgs.put("breadcrumbs", breadcrumbs); addBackUrlToRenderArgs(tenantId, form.owningCategoryId); } private static void addBackUrlToRenderArgs(String tenantId, String categoryId) { String backUrl = request.params.get("return"); if (StringUtils.isBlank(backUrl)) { backUrl = getCategoryUrl(tenantId, categoryId); } renderArgs.put("backUrl", backUrl); } private static void addCategoriesToRenderArgs() { addCategoriesToRenderArgs(Models.currentAdminTenant(), null); } private static void addCategoriesToRenderArgs(String tenantId, String categoryId) { List<CategoryPath> categories = new ArrayList<CategoryPath>(); Map<String, CategoryDef> catalog = getCatalog(tenantId); for (CategoryDef category : catalog.values()) { // Do not add descendants of the specified category, it would cause a circular reference if you tried // assigning a descendant as its parent if (!isDescendantOrSelf(category, categoryId, catalog)) { categories.add(new CategoryPath(category)); } } Collections.sort(categories); renderArgs.put("categories", categories); } /** * Determines if one category is a descendant of another, or the same. * * @param category * the category that is being tested as a descendant. * @param ancestorId * the ID of the ancestor category. * @param catalog * the entire catalog. * @return true if the category is a descendant of the ancestor category specified. */ private static boolean isDescendantOrSelf(CategoryDef category, String ancestorId, Map<String, CategoryDef> catalog) { if ((category == null) || (ancestorId == null)) { return false; } if (StringUtils.equals(category.id, ancestorId)) { return true; } CategoryDef parent = catalog.get(category.parentId); return isDescendantOrSelf(parent, ancestorId, catalog); } private static void addFieldOptions(String baseService) { // Base service may be null for a new service if (StringUtils.isNotBlank(baseService)) { ServiceDescriptorRestRep serviceDescriptor = getServiceDescriptorForEditing(baseService); // Load any Asset Options for root fields so they are rendered directly onto the form List<ServiceFieldRestRep> fields = ServiceDescriptorUtils.getAllFieldList(serviceDescriptor.getItems()); for (ServiceFieldRestRep field : fields) { if (field.isAsset() && field.isLockable()) { try { List<AssetOption> options = AssetOptionUtils.getAssetOptions(field.getAssetType()); request.current().args.put(field.getType() + "-options", options); } catch (RuntimeException e) { request.current().args.put(field.getType() + "-error", e.getMessage()); } } } } } private static void showCategory(String tenantId, String categoryId) { if (StringUtils.isNotBlank(categoryId)) { CategoryDef category = getCatalog(tenantId).get(categoryId); if (category != null) { flash.put("categoryId", categoryId); flash.put("categoryPath", category.path); } } edit(); } private static void edit(CategoryForm category) { addImagesToRenderArgs(); addExecutionWindowsToRenderArgs(); addCategoriesToRenderArgs(Models.currentAdminTenant(), category.id); addBreadCrumbToRenderArgs(Models.currentAdminTenant(), category); addACLsToRenderArgs(); render("@editCategory", category); } public static void createCategory(String parentId, String fromId) { CatalogCategoryRestRep parent = CatalogCategoryUtils.getCatalogCategory(uri(parentId)); CategoryForm category = new CategoryForm(); category.parentId = getId(parent); category.fromId = StringUtils.defaultIfBlank(fromId, category.parentId); edit(category); } public static void editCategory(String categoryId, String fromId) { CatalogCategoryRestRep category = CatalogCategoryUtils.getCatalogCategory(uri(categoryId)); CategoryForm form = new CategoryForm(category); form.fromId = StringUtils.defaultIfBlank(fromId, form.parentId); edit(form); } public static void saveCategory(CategoryForm category) { // Set name before validation if (StringUtils.isNotBlank(category.title)) { category.name = category.title.replaceAll(" ", ""); } category.validate("category"); if (Validation.hasErrors()) { params.flash(); Validation.keep(); if (category.isNew()) { createCategory(category.parentId, category.fromId); } else { editCategory(category.id, category.fromId); } } else { CatalogCategoryRestRep catalogCategory = category.save(); String parentId = getParentId(catalogCategory.getCatalogCategory()); String fromId = StringUtils.defaultIfBlank(category.fromId, parentId); catalogUpdated(fromId, "Saved category: %s", catalogCategory.getTitle()); } } public static void deleteCategory(String categoryId) { CatalogCategoryRestRep category = CatalogCategoryUtils.getCatalogCategory(uri(categoryId)); String title = category.getTitle(); String parentId = getParentId(category.getCatalogCategory()); CatalogCategoryUtils.deleteCatalogCategory(category.getId()); catalogUpdated(parentId, "Category deleted: %s", title); } public static void restoreCatalog() { String tenantId = Models.currentAdminTenant(); try { CatalogCategoryUtils.resetCatalogCategory(uri(tenantId)); catalogModified(tenantId); flash.success(Messages.get("EditCatalog.restored")); edit(); } catch (RuntimeException e) { Logger.error(e, "Failed to restore catalog"); flash.error(Messages.get("EditCatalog.failed")); edit(); } } private static void edit(ServiceForm service) { addImagesToRenderArgs(); addExecutionWindowsToRenderArgs(); addBaseServicesToRenderArgs(); addCategoriesToRenderArgs(); addBreadCrumbToRenderArgs(Models.currentAdminTenant(), service); addACLsToRenderArgs(); // Add information for service fields ServiceDescriptorRestRep serviceDescriptor = null; if (service.baseService != null) { serviceDescriptor = getServiceDescriptorForEditing(service.baseService); addFieldOptions(service.baseService); } render("@editService", service, serviceDescriptor); } protected static ServiceDescriptorRestRep getServiceDescriptorForEditing(String name) { ServiceDescriptorRestRep serviceDescriptor = ServiceDescriptorUtils.getDescriptor(name); if (serviceDescriptor == null) { serviceDescriptor = new CorruptedServiceDescriptor(); } return serviceDescriptor; } public static void createService(String parentId, String fromId) { CatalogCategoryRestRep parentCategory = CatalogCategoryUtils.getCatalogCategory(uri(parentId)); ServiceForm service = new ServiceForm(); service.owningCategoryId = getId(parentCategory); service.fromId = StringUtils.defaultIfBlank(fromId, service.owningCategoryId); edit(service); } public static void editService(String serviceId, String fromId) { CatalogServiceRestRep catalogService = CatalogServiceUtils.getCatalogService(uri(serviceId)); ServiceForm service = new ServiceForm(catalogService); service.fromId = StringUtils.defaultIfBlank(fromId, service.owningCategoryId); edit(service); } public static void copyService(String serviceId, String fromId) { CatalogServiceRestRep catalogService = CatalogServiceUtils.getCatalogService(uri(serviceId)); ServiceForm service = new ServiceForm(catalogService); service.id = null; service.title = Messages.get("EditCatalog.copyOf", service.title); service.fromId = StringUtils.defaultIfBlank(fromId, service.owningCategoryId); edit(service); } public static void deleteService(String serviceId) { CatalogServiceRestRep catalogService = CatalogServiceUtils.getCatalogService(uri(serviceId)); deleteService(catalogService); } private static void deleteService(CatalogServiceRestRep catalogService) { String title = catalogService.getTitle(); String parentId = getParentId(catalogService.getCatalogCategory()); CatalogServiceUtils.deleteCatalogService(catalogService.getId()); catalogUpdated(parentId, "EditCatalog.deletedService", title); } public static void saveService(ServiceForm service) { // Set name before validation if (StringUtils.isNotBlank(service.title)) { service.name = service.title.replaceAll(" ", ""); } service.validate("service"); if (validation.hasErrors()) { params.flash(); validation.keep(); if (service.isNew()) { createService(service.owningCategoryId, service.fromId); } else { editService(service.id, service.fromId); } } else { CatalogServiceRestRep catalogService = service.save(); String parentId = getParentId(catalogService.getCatalogCategory()); String fromId = StringUtils.defaultIfBlank(service.fromId, parentId); catalogUpdated(fromId, "EditCatalog.savedService", catalogService.getTitle()); } } public static void serviceFields(String baseService, String serviceId) { ServiceDescriptorRestRep serviceDescriptor = ServiceDescriptorUtils.getDescriptor(baseService); ServiceForm service = null; if (StringUtils.isNotBlank(serviceId)) { CatalogServiceRestRep dbService = CatalogServiceUtils.getCatalogService(uri(serviceId)); if (dbService != null) { service = new ServiceForm(dbService); } else { service = new ServiceForm(); service.baseService = baseService; } } addFieldOptions(baseService); render(serviceDescriptor, service); } public static void services(String id) { List<CatalogServiceRestRep> catalogServices = null; if (StringUtils.isNotBlank(id)) { CatalogCategoryRestRep parent = CatalogCategoryUtils.getCatalogCategory(uri(id)); catalogServices = CatalogServiceUtils.getCatalogServices(parent); } render(catalogServices); } public static void categories(String id) { List<CatalogCategoryRestRep> catalogCategories = null; if (StringUtils.isNotBlank(id)) { CatalogCategoryRestRep parent = CatalogCategoryUtils.getCatalogCategory(uri(id)); catalogCategories = CatalogCategoryUtils.getCatalogCategories(parent); } render(catalogCategories); } public static void moveUpService(String id) { CatalogServiceRestRep catalogService = CatalogServiceUtils.getCatalogService(uri(id)); CatalogServiceUtils.moveUpService(catalogService.getId()); services(catalogService.getCatalogCategory().getId().toString()); } public static void moveDownService(String id) { CatalogServiceRestRep catalogService = CatalogServiceUtils.getCatalogService(uri(id)); CatalogServiceUtils.moveDownService(catalogService.getId()); services(catalogService.getCatalogCategory().getId().toString()); } public static void moveUpCategory(String id) { CatalogCategoryRestRep catalogCategory = CatalogCategoryUtils.getCatalogCategory(uri(id)); CatalogCategoryUtils.moveUpCategory(catalogCategory.getId()); categories(catalogCategory.getCatalogCategory().getId().toString()); } public static void moveDownCategory(String id) { CatalogCategoryRestRep catalogCategory = CatalogCategoryUtils.getCatalogCategory(uri(id)); CatalogCategoryUtils.moveDownCategory(catalogCategory.getId()); categories(catalogCategory.getCatalogCategory().getId().toString()); } /** * Gets the ID from a model, or null if the model is null. * * @param model * the model. * @return the ID. */ public static String getId(DataObjectRestRep model) { return model != null && model.getId() != null ? model.getId().toString() : null; } /** * Represents the service edited in the forms. * * @author jonnymiller */ public static class ServiceForm extends AbstractRestRepForm<CatalogServiceRestRep> { /** The category that this edit was launched from. */ public String fromId; @Required public String name; @Required @CheckWith(CatalogServiceNameUniqueCheck.class) @MaxSize(128) @MinSize(2) public String title; @Required @MaxSize(255) public String description; @Required @MaxSize(255) public String image; @Required @MaxSize(255) public String baseService; public String owningCategoryId; @Min(1) public Integer maxSize; public Boolean approvalRequired; public Boolean executionWindowRequired; public String defaultExecutionWindowId; public List<AclEntryForm> aclEntries = Lists.newArrayList(); @CheckWith(ServiceFieldsCheck.class) public List<ServiceFieldForm> serviceFields = Lists.newArrayList(); public ServiceForm() { } public ServiceForm(CatalogServiceRestRep service) { this(); readFrom(service); } @Override public void doReadFrom(CatalogServiceRestRep service) { this.id = service.getId().toString(); this.name = service.getName(); this.title = service.getTitle(); this.description = service.getDescription(); this.image = service.getImage(); this.baseService = service.getBaseService(); this.maxSize = (service.getMaxSize() != null && service.getMaxSize() > 0) ? service.getMaxSize() : null; if (service.getCatalogCategory() != null) { this.owningCategoryId = service.getCatalogCategory().getId().toString(); } this.approvalRequired = service.isApprovalRequired(); this.executionWindowRequired = service.isExecutionWindowRequired(); if (service.getDefaultExecutionWindow() != null) { this.defaultExecutionWindowId = service.getDefaultExecutionWindow().getId().toString(); } this.serviceFields.clear(); List<CatalogServiceFieldRestRep> catalogServiceFields = service.getCatalogServiceFields(); if (catalogServiceFields != null) { for (CatalogServiceFieldRestRep catalogServiceField : catalogServiceFields) { ServiceFieldForm serviceFieldForm = new ServiceFieldForm(catalogServiceField); this.serviceFields.add(serviceFieldForm); } } this.aclEntries.clear(); this.aclEntries.addAll(ACLUtils.convertToAclEntryForms(CatalogServiceUtils.getACLs(id))); } @Override protected void doValidation(String fieldName) { ACLUtils.validateAclEntries(fieldName + ".aclEntries", this.aclEntries); // ensure the service descriptor is valid ServiceDescriptorRestRep descriptor = ServiceDescriptorUtils.getDescriptor(this.baseService); if (descriptor == null) { String fieldPath = fieldName + ".baseService"; Validation.addError(fieldPath, "service.baseService.notFound"); } } @Override protected CatalogServiceRestRep doCreate() { CatalogServiceRestRep catalogService = null; try { CatalogServiceCreateParam createParam = new CatalogServiceCreateParam(); writeCommon(createParam); catalogService = CatalogServiceUtils.createCatalogService(createParam); ACLUtils.updateACLs(getCatalogClient().services(), catalogService.getId(), this.aclEntries); } catch (Exception e) { Common.flashException(e); Common.handleError(); } return catalogService; } @Override protected CatalogServiceRestRep doUpdate() { CatalogServiceRestRep catalogService = null; try { CatalogServiceUpdateParam updateParam = new CatalogServiceUpdateParam(); writeCommon(updateParam); catalogService = CatalogServiceUtils.updateCatalogService(uri(this.id), updateParam); ACLUtils.updateACLs(getCatalogClient().services(), catalogService.getId(), this.aclEntries); } catch (Exception e) { Common.flashException(e); Common.handleError(); } return catalogService; } private void writeCommon(CatalogServiceCommonParam commonParam) { commonParam.setName(title.replaceAll(" ", "")); commonParam.setTitle(title); commonParam.setDescription(description); commonParam.setImage(image); commonParam.setBaseService(baseService); if (StringUtils.isNotBlank(owningCategoryId)) { CatalogCategoryRestRep parent = CatalogCategoryUtils.getCatalogCategory(uri(this.owningCategoryId)); if (parent != null) { commonParam.setCatalogCategory(uri(owningCategoryId)); } } commonParam.setApprovalRequired(this.approvalRequired); commonParam.setExecutionWindowRequired(this.executionWindowRequired); commonParam.setMaxSize(this.maxSize != null ? this.maxSize : 0); if (StringUtils.isNotBlank(this.defaultExecutionWindowId) && "NEXT".equalsIgnoreCase(this.defaultExecutionWindowId) == false) { ExecutionWindowRestRep executionWindow = ExecutionWindowUtils.getExecutionWindow(uri(this.defaultExecutionWindowId)); if (executionWindow != null) { commonParam.setDefaultExecutionWindow(uri(this.defaultExecutionWindowId)); } else { commonParam.setDefaultExecutionWindow(null); } } else { commonParam.setDefaultExecutionWindow(null); } for (ServiceFieldForm serviceFieldForm : this.serviceFields) { CatalogServiceFieldParam fieldParam = new CatalogServiceFieldParam(); serviceFieldForm.writeTo(fieldParam); commonParam.getCatalogServiceFields().add(fieldParam); } } /* * Used by validation */ public void writeTo(CatalogServiceRestRep service) { service.setName(title.replaceAll(" ", "")); service.setTitle(title); service.setDescription(description); service.setImage(image); service.setBaseService(baseService); // if (StringUtils.isNotBlank(owningCategoryId)) { // CatalogCategoryRestRep parent = CatalogCategoryUtils.getCatalogCategory(uri(this.owningCategoryId)); // if (parent != null) { // service.setCatalogCategoryId(uri(owningCategoryId)); // } // } service.setApprovalRequired(this.approvalRequired); service.setExecutionWindowRequired(this.executionWindowRequired); service.setMaxSize(this.maxSize != null ? this.maxSize : 0); // if (this.defaultExecutionWindowId != null) { // ExecutionWindowRestRep executionWindow = ExecutionWindowUtils.getExecutionWindow(uri(this.defaultExecutionWindowId)); // if (executionWindow != null) { // commonParam.setDefaultExecutionWindow(uri(this.defaultExecutionWindowId)); // } // else { // commonParam.setDefaultExecutionWindow(null); // } // } // else { // commonParam.setDefaultExecutionWindow(null); // } // // for (ServiceFieldForm serviceFieldForm : this.serviceFields) { // CatalogServiceFieldParam fieldParam = new CatalogServiceFieldParam(); // serviceFieldForm.writeTo(fieldParam); // commonParam.getCatalogServiceFields().add(fieldParam); // } } } public static boolean isSameCategory(URI categoryId, String id) { return (categoryId != null && categoryId.toString().equals(id)); } public static class ServiceFieldForm { @Required public String name; public boolean override = false; public String value; public ServiceFieldForm() { } public ServiceFieldForm(CatalogServiceFieldRestRep catalogServiceField) { this(); doReadFrom(catalogServiceField); } public void doReadFrom(CatalogServiceFieldRestRep catalogServiceField) { this.name = catalogServiceField.getName(); this.override = catalogServiceField.getOverride(); this.value = catalogServiceField.getValue(); } public void writeTo(CatalogServiceFieldParam fieldParam) { fieldParam.setName(this.name); fieldParam.setOverride(this.override); fieldParam.setValue(this.value); } } /** * Represents the category edited in the forms. * * @author jonnymiller */ public static class CategoryForm extends AbstractRestRepForm<CatalogCategoryRestRep> { /** The category that this edit was launched from. */ public String fromId; @Required public String name; @Required @MaxSize(128) @MinSize(2) @CheckWith(CatalogCategoryNameUniqueCheck.class) public String title; @Required @MaxSize(255) public String description; @Required @MaxSize(255) public String image; public String parentId; public List<AclEntryForm> aclEntries = Lists.newArrayList(); public CategoryForm() { } public CategoryForm(CatalogCategoryRestRep category) { this(); readFrom(category); } public boolean isCatalogRoot() { return parentId == null; } @Override public void doReadFrom(CatalogCategoryRestRep category) { this.name = category.getName(); this.title = category.getTitle(); this.description = category.getDescription(); this.image = category.getImage(); if (category.getCatalogCategory() != null) { this.parentId = category.getCatalogCategory().getId().toString(); } this.aclEntries.clear(); this.aclEntries.addAll(ACLUtils.convertToAclEntryForms(CatalogCategoryUtils.getACLs(this.id))); } @Override protected void doValidation(String fieldName) { ACLUtils.validateAclEntries(fieldName + ".aclEntries", this.aclEntries); } @Override protected CatalogCategoryRestRep doCreate() { CatalogCategoryRestRep catalogCategory = null; try { CatalogCategoryCreateParam createParam = new CatalogCategoryCreateParam(); createParam.setTenantId(getCategoryTenant()); writeCommon(createParam); catalogCategory = CatalogCategoryUtils.createCatalogCategory(createParam); ACLUtils.updateACLs(getCatalogClient().categories(), catalogCategory.getId(), this.aclEntries); } catch (Exception e) { Common.flashException(e); Common.handleError(); } return catalogCategory; } @Override protected CatalogCategoryRestRep doUpdate() { CatalogCategoryRestRep catalogCategory = null; try { CatalogCategoryUpdateParam updateParam = new CatalogCategoryUpdateParam(); writeCommon(updateParam); catalogCategory = CatalogCategoryUtils.updateCatalogCategory(uri(this.id), updateParam); ACLUtils.updateACLs(getCatalogClient().categories(), catalogCategory.getId(), this.aclEntries); } catch (Exception e) { Common.flashException(e); Common.handleError(); } return catalogCategory; } private void writeCommon(CatalogCategoryCommonParam commonParam) { commonParam.setName(title.replaceAll(" ", "")); commonParam.setTitle(title); commonParam.setDescription(description); commonParam.setImage(image); if (!isCatalogRoot()) { commonParam.setCatalogCategoryId(uri(parentId)); } } private String getCategoryTenant() { String tenantId = Models.currentAdminTenant(); if (StringUtils.isBlank(tenantId)) { tenantId = Security.getUserInfo().getTenant(); } return tenantId; } } /** * Class for selecting parent categories. * * @author jonnymiller */ public static class CategoryPath implements Comparable<CategoryPath> { public String id; public String title; public String label; public String path; public CategoryPath(CategoryDef category) { this.id = category.id; this.title = category.title; this.path = category.path; int depth = StringUtils.countMatches(path, "/"); this.label = StringUtils.repeat("-", depth) + " " + category.title; } @Override public int compareTo(CategoryPath arg0) { return path.compareToIgnoreCase(arg0.path); } } /** * Determines if the service name is unique within a parent category. * * @param id * the service ID, if editing a service. * @param name * the service name. * @param parentId * the parent category ID. * @return true if the service name is unique. */ private static boolean isUniqueServiceName(String id, String name, String parentId) { CatalogCategoryRestRep parentCatalogCategory = CatalogCategoryUtils.getCatalogCategory(uri(parentId)); if (parentCatalogCategory != null) { List<CatalogServiceRestRep> catalogServices = CatalogServiceUtils.getCatalogServices(parentCatalogCategory); for (CatalogServiceRestRep catalogService : catalogServices) { if (catalogService.getId().toString().equals(id) == false && name.equalsIgnoreCase(catalogService.getName())) { return false; } } } return true; } /** * Determines if the category name is unique within a parent category. * * @param id * the category ID. * @param name * the category name. * @param parentId * the parent category ID. * @return true if the category name is unique. */ private static boolean isUniqueCategoryName(String id, String name, String parentId) { if (parentId == null) { return true; } CatalogCategoryRestRep parentCatalogCategory = CatalogCategoryUtils.getCatalogCategory(uri(parentId)); if (parentCatalogCategory != null) { List<CatalogCategoryRestRep> subCatalogCategories = CatalogCategoryUtils.getCatalogCategories(parentCatalogCategory); for (CatalogCategoryRestRep subCatalogCategory : subCatalogCategories) { if (subCatalogCategory.getId().toString().equals(id) == false && name.equalsIgnoreCase(subCatalogCategory.getName())) { return false; } } } return true; } public class CatalogServiceNameUniqueCheck extends Check { @Override public boolean isSatisfied(Object validatedObject, Object value) { if (validatedObject instanceof ServiceForm) { ServiceForm serviceForm = (ServiceForm) validatedObject; String title = (String) value; if (title != null) { String name = title.replaceAll(" ", ""); if (isUniqueName(serviceForm, name)) { return true; } } } setMessage("catalogService.name.notUnique"); return false; } /** * Determines if the service name is unique within the parent category. * * @param service * the service form input. * @param name * the service name. * @return true if the name is unique. */ private boolean isUniqueName(ServiceForm service, String name) { String parentId = service.owningCategoryId; return isUniqueServiceName(service.id, name, parentId) && isUniqueCategoryName(null, name, parentId); } } public class ServiceFieldsCheck extends Check { public ServiceFieldsCheck() { setMessage("service.field.validation"); } @Override public boolean isSatisfied(Object validatedObject, Object value) { if (validatedObject instanceof ServiceForm) { ServiceForm serviceForm = (ServiceForm) validatedObject; List<ServiceFieldForm> serviceFields = (List<ServiceFieldForm>) value; // The catalog service may have not been saved, create a temporary one CatalogServiceRestRep catalogServiceTemp = new CatalogServiceRestRep(); serviceForm.writeTo(catalogServiceTemp); ServiceDescriptorRestRep serviceDescriptor = ServiceDescriptorUtils.getDescriptor(serviceForm.baseService); if (serviceDescriptor != null) { for (ServiceFieldForm field : serviceFields) { if (!field.override) { continue; } String fieldName = field.name; String fieldValue = field.value; ServiceFieldRestRep descriptorField = ServiceDescriptorUtils.getField(serviceDescriptor, fieldName); if (descriptorField != null) { ServiceFieldValidator.validateField(catalogServiceTemp, descriptorField, fieldValue); } } } } return true; } } public class CatalogCategoryNameUniqueCheck extends Check { @Override public boolean isSatisfied(Object validatedObject, Object value) { if (validatedObject instanceof CategoryForm) { CategoryForm categoryForm = (CategoryForm) validatedObject; if (categoryForm.isCatalogRoot()) { return true; } String title = (String) value; if (title != null) { String name = title.replaceAll(" ", ""); if (isUniqueName(categoryForm, name)) { return true; } } } setMessage("catalogCategory.name.notUnique"); return false; } /** * Determines if the category name is unique within the parent category. * * @param category * the category form input. * @return true if the name is unique. */ private boolean isUniqueName(CategoryForm category, String name) { String parentId = category.parentId; return isUniqueCategoryName(category.id, name, parentId) && isUniqueServiceName(null, name, parentId); } } public static class ServiceDescriptorComparator implements Comparator<ServiceDescriptorRestRep> { @Override public int compare(ServiceDescriptorRestRep a, ServiceDescriptorRestRep b) { int result = stringCompare(a.getCategory(), b.getCategory()); if (result == 0) { result = stringCompare(a.getDescription(), b.getDescription()); } return result; } private int stringCompare(String a, String b) { a = StringUtils.defaultString(a); b = StringUtils.defaultString(b); int result = a.compareTo(b); if (result == 0) { return a.compareToIgnoreCase(b); } return result; } } public static class CorruptedServiceDescriptor extends ServiceDescriptorRestRep { private static final String UNKNOWN_VALUE = "UNKNOWN"; public CorruptedServiceDescriptor() { setServiceId(UNKNOWN_VALUE); setCategory(UNKNOWN_VALUE); setTitle(UNKNOWN_VALUE); setDescription(UNKNOWN_VALUE); }; } public static void updateCatalog() throws Exception { CatalogCategoryUtils.upgradeCatalog(); catalogModified(Models.currentAdminTenant()); flash.success(Messages.get("EditCatalog.updated")); edit(); } /** * Handles errors that might arise during JSON requests and returns the error message. * * @param e */ @Catch({ UnexpectedException.class, ViPRException.class }) public static void handleJsonError(Exception e) { if (request.isAjax() || StringUtils.endsWithIgnoreCase(request.action, "json")) { Throwable cause = Common.unwrap(e); String message = Common.getUserMessage(cause); Logger.error(e, "AJAX request failed: %s.%s [%s]", request.controller, request.action, message); error(message); } } }