You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by tb...@apache.org on 2014/11/12 21:22:14 UTC
ambari git commit: AMBARI-8295 - Views: support validation
(tbeerbower)
Repository: ambari
Updated Branches:
refs/heads/trunk 252e94c63 -> e2757784c
AMBARI-8295 - Views: support validation (tbeerbower)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e2757784
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e2757784
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e2757784
Branch: refs/heads/trunk
Commit: e2757784ca251c18ecdd248a7a1a8e4a3019fac3
Parents: 252e94c
Author: tbeerbower <tb...@hortonworks.com>
Authored: Wed Nov 12 15:21:40 2014 -0500
Committer: tbeerbower <tb...@hortonworks.com>
Committed: Wed Nov 12 15:22:10 2014 -0500
----------------------------------------------------------------------
.../controller/internal/BaseProvider.java | 69 ++++++++++--
.../internal/ViewInstanceResourceProvider.java | 23 ++++
.../ambari/server/orm/entities/ViewEntity.java | 34 ++++++
.../server/orm/entities/ViewInstanceEntity.java | 73 ++++++++++---
.../apache/ambari/server/view/ViewRegistry.java | 54 +++++++---
.../server/view/configuration/ViewConfig.java | 37 +++++++
.../InstanceValidationResultImpl.java | 106 +++++++++++++++++++
.../view/validation/ValidationResultImpl.java | 80 ++++++++++++++
.../controller/internal/BaseProviderTest.java | 28 +++++
.../server/orm/entities/ViewEntityTest.java | 27 +++++
.../orm/entities/ViewInstanceEntityTest.java | 79 +++++++++++++-
.../view/configuration/ViewConfigTest.java | 24 +++++
.../InstanceValidationResultImplTest.java | 66 ++++++++++++
.../validation/ValidationResultImplTest.java | 55 ++++++++++
.../view/validation/ValidationResult.java | 54 ++++++++++
.../ambari/view/validation/Validator.java | 59 +++++++++++
ambari-views/src/main/resources/view.xsd | 5 +
17 files changed, 835 insertions(+), 38 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseProvider.java
index b4d582a..a95342a 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseProvider.java
@@ -279,6 +279,21 @@ public abstract class BaseProvider {
}
/**
+ * Determine whether or not the given property id is part the given set of requested ids. This
+ * accounts for the cases where the given properties are actually property categories.
+ *
+ * @param propertyId the property id
+ * @param requestedIds the requested set of property ids
+ *
+ * @return true if the given property id is part of the given set of requested ids
+ */
+ protected static boolean isPropertyRequested(String propertyId, Set<String> requestedIds) {
+ return requestedIds.contains(propertyId) ||
+ isPropertyCategoryRequested(propertyId, requestedIds) ||
+ isPropertyEntryRequested(propertyId, requestedIds);
+ }
+
+ /**
* Set a property value on the given resource for the given id and value.
* Make sure that the id is in the given set of requested ids.
*
@@ -287,16 +302,9 @@ public abstract class BaseProvider {
* @param value the value to set
* @param requestedIds the requested set of property ids
*/
- protected static boolean setResourceProperty(Resource resource, String propertyId, Object value, Set<String> requestedIds) {
- boolean contains = requestedIds.contains(propertyId);
-
- if (!contains) {
- String category = PropertyHelper.getPropertyCategory(propertyId);
- while (category != null && !contains) {
- contains = requestedIds.contains(category);
- category = PropertyHelper.getPropertyCategory(category);
- }
- }
+ protected static boolean setResourceProperty(Resource resource, String propertyId, Object value,
+ Set<String> requestedIds) {
+ boolean contains = requestedIds.contains(propertyId) || isPropertyCategoryRequested(propertyId, requestedIds);
if (contains) {
if (LOG.isDebugEnabled()) {
@@ -364,6 +372,47 @@ public abstract class BaseProvider {
return false;
}
+ /**
+ * Determine whether or not any of the requested ids are an entry for the given property, if
+ * the given property is a category. For example, if the given property is 'category/subcategory'
+ * and the set of requested ids contains 'category/subcategory/property' then this method should
+ * return true.
+ *
+ * @param propertyId the property id
+ * @param requestedIds the requested set of property ids
+ *
+ * @return true if the given property is a category for any of the requested ids
+ */
+ private static boolean isPropertyEntryRequested(String propertyId, Set<String> requestedIds) {
+ for (String requestedId : requestedIds) {
+ if (requestedId.startsWith(propertyId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether or not any of the requested ids are a category for the given property.
+ * For example, if the given property is 'category/subcategory/property' and the set of requested ids
+ * contains 'category' or 'category/subcategory' then this method should return true.
+ *
+ * @param propertyId the property id
+ * @param requestedIds the requested set of property ids
+ *
+ * @return true if the given property's category is part of the given set of requested ids
+ */
+ private static boolean isPropertyCategoryRequested(String propertyId, Set<String> requestedIds) {
+ String category = PropertyHelper.getPropertyCategory(propertyId);
+ while (category != null ) {
+ if (requestedIds.contains(category)) {
+ return true;
+ }
+ category = PropertyHelper.getPropertyCategory(category);
+ }
+ return false;
+ }
+
// ----- accessors ---------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
index 3f62cc3..a944e95 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
@@ -36,6 +36,9 @@ import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
import org.apache.ambari.server.orm.entities.ViewInstancePropertyEntity;
import org.apache.ambari.server.orm.entities.ViewParameterEntity;
import org.apache.ambari.server.view.ViewRegistry;
+import org.apache.ambari.server.view.validation.InstanceValidationResultImpl;
+import org.apache.ambari.server.view.validation.ValidationResultImpl;
+import org.apache.ambari.view.validation.Validator;
import java.util.Collections;
import java.util.HashMap;
@@ -65,6 +68,10 @@ public class ViewInstanceResourceProvider extends AbstractResourceProvider {
public static final String CONTEXT_PATH_PROPERTY_ID = "ViewInstanceInfo/context_path";
public static final String STATIC_PROPERTY_ID = "ViewInstanceInfo/static";
+ // validation properties
+ public static final String VALIDATION_RESULT_PROPERTY_ID = "ViewInstanceInfo/validation_result";
+ public static final String PROPERTY_VALIDATION_RESULTS_PROPERTY_ID = "ViewInstanceInfo/property_validation_results";
+
/**
* Property prefix values.
*/
@@ -98,6 +105,8 @@ public class ViewInstanceResourceProvider extends AbstractResourceProvider {
propertyIds.add(DATA_PROPERTY_ID);
propertyIds.add(CONTEXT_PATH_PROPERTY_ID);
propertyIds.add(STATIC_PROPERTY_ID);
+ propertyIds.add(VALIDATION_RESULT_PROPERTY_ID);
+ propertyIds.add(PROPERTY_VALIDATION_RESULTS_PROPERTY_ID);
}
// ----- Constructors ------------------------------------------------------
@@ -255,6 +264,20 @@ public class ViewInstanceResourceProvider extends AbstractResourceProvider {
setResourceProperty(resource, ICON_PATH_ID, getIconPath(contextPath, viewInstanceEntity.getIcon()), requestedIds);
setResourceProperty(resource, ICON64_PATH_ID, getIconPath(contextPath, viewInstanceEntity.getIcon64()), requestedIds);
+ // if the view provides its own validator then run it
+ if (viewEntity.hasValidator()) {
+
+ if (isPropertyRequested(VALIDATION_RESULT_PROPERTY_ID, requestedIds) ||
+ isPropertyRequested(PROPERTY_VALIDATION_RESULTS_PROPERTY_ID, requestedIds)) {
+
+ InstanceValidationResultImpl result =
+ viewInstanceEntity.getValidationResult(viewEntity, Validator.ValidationContext.EXISTING);
+
+ setResourceProperty(resource, VALIDATION_RESULT_PROPERTY_ID, ValidationResultImpl.create(result), requestedIds);
+ setResourceProperty(resource, PROPERTY_VALIDATION_RESULTS_PROPERTY_ID, result.getPropertyResults(), requestedIds);
+ }
+ }
+
return resource;
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java
index d42e1a0..2f83e02 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java
@@ -24,6 +24,7 @@ import org.apache.ambari.server.controller.spi.ResourceProvider;
import org.apache.ambari.server.view.ViewSubResourceDefinition;
import org.apache.ambari.server.view.configuration.ResourceConfig;
import org.apache.ambari.server.view.configuration.ViewConfig;
+import org.apache.ambari.view.validation.Validator;
import org.apache.ambari.view.View;
import org.apache.ambari.view.ViewDefinition;
@@ -211,6 +212,12 @@ public class ViewEntity implements ViewDefinition {
private View view = null;
/**
+ * The view validator.
+ */
+ @Transient
+ private Validator validator = null;
+
+ /**
* The view status.
*/
@Transient
@@ -705,6 +712,33 @@ public class ViewEntity implements ViewDefinition {
}
/**
+ * Set the view validator.
+ *
+ * @param validator the view validator
+ */
+ public void setValidator(Validator validator) {
+ this.validator = validator;
+ }
+
+ /**
+ * Get the associated view validator.
+ *
+ * @return the view validator
+ */
+ public Validator getValidator() {
+ return validator;
+ }
+
+ /**
+ * Determine whether or not a validator has been specified for this view.
+ *
+ * @return true if this view has a validator
+ */
+ public boolean hasValidator() {
+ return validator != null;
+ }
+
+ /**
* Set the mask class name.
*
* @param mask the mask class name
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
index efa2818..1dd1b5a 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
@@ -48,9 +48,13 @@ import org.apache.ambari.server.security.SecurityHelper;
import org.apache.ambari.server.security.SecurityHelperImpl;
import org.apache.ambari.server.security.authorization.AmbariAuthorizationFilter;
import org.apache.ambari.server.view.configuration.InstanceConfig;
+import org.apache.ambari.server.view.validation.InstanceValidationResultImpl;
+import org.apache.ambari.server.view.validation.ValidationResultImpl;
+import org.apache.ambari.view.validation.Validator;
import org.apache.ambari.view.ResourceProvider;
import org.apache.ambari.view.ViewDefinition;
import org.apache.ambari.view.ViewInstanceDefinition;
+import org.apache.ambari.view.validation.ValidationResult;
/**
* Represents an instance of a View.
@@ -158,7 +162,7 @@ public class ViewInstanceEntity implements ViewInstanceDefinition {
@OneToOne(cascade = CascadeType.ALL)
@JoinColumns({
- @JoinColumn(name = "resource_id", referencedColumnName = "resource_id", nullable = false),
+ @JoinColumn(name = "resource_id", referencedColumnName = "resource_id", nullable = false)
})
private ResourceEntity resource;
@@ -712,26 +716,67 @@ public class ViewInstanceEntity implements ViewInstanceDefinition {
* Validate the state of the instance.
*
* @param viewEntity the view entity to which this instance will be bound
+ * @param context the validation context
+ *
* @throws IllegalStateException if the instance is not in a valid state
*/
- public void validate(ViewEntity viewEntity) throws IllegalStateException {
+ public void validate(ViewEntity viewEntity, Validator.ValidationContext context) throws IllegalStateException {
+ InstanceValidationResultImpl result = getValidationResult(viewEntity, context);
+ if (!result.isValid()) {
+ throw new IllegalStateException(result.toJson());
+ }
+ }
- // make sure that there is an instance property value defined
- // for each required view parameter
- Set<String> requiredParamterNames = new HashSet<String>();
- for (ViewParameterEntity parameter : viewEntity.getParameters()) {
- if (parameter.isRequired()) {
- requiredParamterNames.add(parameter.getName());
+ /**
+ * Get the validation the state of the instance.
+ *
+ * @param viewEntity the view entity to which this instance will be bound
+ * @param context the validation context
+ *
+ * @return the instance validation result
+ */
+ public InstanceValidationResultImpl getValidationResult(ViewEntity viewEntity, Validator.ValidationContext context)
+ throws IllegalStateException {
+
+ Map<String, ValidationResult> propertyResults = new HashMap<String, ValidationResult>();
+
+ if (context.equals(Validator.ValidationContext.PRE_CREATE) ||
+ context.equals(Validator.ValidationContext.PRE_UPDATE)) {
+
+ // make sure that there is an instance property value defined
+ // for each required view parameter
+ Set<String> requiredParameterNames = new HashSet<String>();
+ for (ViewParameterEntity parameter : viewEntity.getParameters()) {
+ if (parameter.isRequired()) {
+ requiredParameterNames.add(parameter.getName());
+ }
+ }
+ Collection<ViewInstancePropertyEntity> propertyEntities = getProperties();
+ for (ViewInstancePropertyEntity property : propertyEntities) {
+ requiredParameterNames.remove(property.getName());
+ }
+ // required but missing instance properties...
+ for (String requiredParameterName : requiredParameterNames) {
+ propertyResults.put(requiredParameterName,
+ new ValidationResultImpl(false,
+ "No property values exist for the required parameter " + requiredParameterName + "."));
}
- }
- for (ViewInstancePropertyEntity property : getProperties()) {
- requiredParamterNames.remove(property.getName());
}
- if (!requiredParamterNames.isEmpty()) {
- throw new IllegalStateException("No property values exist for the required parameters " +
- requiredParamterNames);
+ ValidationResult instanceResult = null;
+ Validator validator = viewEntity.getValidator();
+
+ // if the view provides its own validator, run it
+ if (validator != null) {
+ instanceResult = validator.validateInstance(this, context);
+ for ( String property : getPropertyMap().keySet()) {
+ if (!propertyResults.containsKey(property)) {
+ propertyResults.put(property,
+ ValidationResultImpl.create(validator.validateProperty(property, this, context)));
+ }
+ }
}
+ return new InstanceValidationResultImpl(ValidationResultImpl.create(instanceResult), propertyResults);
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
index a5d11bd..0dbf32c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
@@ -22,6 +22,7 @@ import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import com.google.inject.persist.Transactional;
import org.apache.ambari.server.api.resources.ResourceInstanceFactoryImpl;
import org.apache.ambari.server.api.resources.SubResourceDefinition;
import org.apache.ambari.server.api.resources.ViewExternalSubResourceDefinition;
@@ -64,6 +65,7 @@ import org.apache.ambari.server.view.configuration.PersistenceConfig;
import org.apache.ambari.server.view.configuration.PropertyConfig;
import org.apache.ambari.server.view.configuration.ResourceConfig;
import org.apache.ambari.server.view.configuration.ViewConfig;
+import org.apache.ambari.view.validation.Validator;
import org.apache.ambari.view.Masker;
import org.apache.ambari.view.SystemException;
import org.apache.ambari.view.View;
@@ -461,6 +463,7 @@ public class ViewRegistry {
* does not exist
* @throws SystemException if the instance can not be installed
*/
+ @Transactional
public void installViewInstance(ViewInstanceEntity instanceEntity)
throws IllegalStateException, IllegalArgumentException, SystemException {
ViewEntity viewEntity = getDefinition(instanceEntity.getViewName());
@@ -476,7 +479,7 @@ public class ViewRegistry {
version + "/" + instanceName);
}
- instanceEntity.validate(viewEntity);
+ instanceEntity.validate(viewEntity, Validator.ValidationContext.PRE_CREATE);
ResourceTypeEntity resourceTypeEntity = resourceTypeDAO.findByName(ViewEntity.getViewName(viewName, version));
// create an admin resource to represent this view instance
@@ -530,7 +533,7 @@ public class ViewRegistry {
ViewEntity viewEntity = getDefinition(instanceEntity.getViewName());
if (viewEntity != null) {
- instanceEntity.validate(viewEntity);
+ instanceEntity.validate(viewEntity, Validator.ValidationContext.PRE_UPDATE);
instanceDAO.merge(instanceEntity);
}
}
@@ -541,6 +544,7 @@ public class ViewRegistry {
* @param instanceEntity the view instance entity
* @throws IllegalStateException if the given instance is not in a valid state
*/
+ @Transactional
public void uninstallViewInstance(ViewInstanceEntity instanceEntity) throws IllegalStateException {
ViewEntity viewEntity = getDefinition(instanceEntity.getViewName());
@@ -577,6 +581,7 @@ public class ViewRegistry {
* @param instanceEntity the instance entity
* @param key the data key
*/
+ @Transactional
public void removeInstanceData(ViewInstanceEntity instanceEntity, String key) {
ViewInstanceDataEntity dataEntity = instanceEntity.getInstanceData(key);
if (dataEntity != null) {
@@ -903,6 +908,11 @@ public class ViewRegistry {
view = getView(viewConfig.getViewClass(cl), new ViewContextImpl(viewDefinition, this));
}
viewDefinition.setView(view);
+ Validator validator = null;
+ if (viewConfig.getValidator() != null) {
+ validator = getValidator(viewConfig.getValidatorClass(cl), new ViewContextImpl(viewDefinition, this));
+ }
+ viewDefinition.setValidator(validator);
viewDefinition.setMask(viewConfig.getMasker());
Set<SubResourceDefinition> subResourceDefinitions = new HashSet<SubResourceDefinition>();
@@ -927,7 +937,7 @@ public class ViewRegistry {
properties.put(propertyConfig.getKey(), propertyConfig.getValue());
}
setViewInstanceProperties(viewInstanceDefinition, properties, viewConfig, viewDefinition.getClassLoader());
- viewInstanceDefinition.validate(viewDefinition);
+ viewInstanceDefinition.validate(viewDefinition, Validator.ValidationContext.PRE_CREATE);
bindViewInstance(viewDefinition, viewInstanceDefinition);
return viewInstanceDefinition;
@@ -1042,6 +1052,19 @@ public class ViewRegistry {
return viewInstanceInjector.getInstance(clazz);
}
+ // get the given view validator class from the given class loader; inject a context
+ private static Validator getValidator(Class<? extends Validator> clazz,
+ final ViewContext viewContext) {
+ Injector viewInstanceInjector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ViewContext.class)
+ .toInstance(viewContext);
+ }
+ });
+ return viewInstanceInjector.getInstance(clazz);
+ }
+
// create masker from given class; probably replace with injector later
private static Masker getMasker(Class<? extends Masker> clazz) {
try {
@@ -1302,16 +1325,8 @@ public class ViewRegistry {
instanceEntity.setXmlDriven(true);
instanceDefinitions.add(instanceEntity);
}
- // ensure that the view entity matches the db
- syncView(viewDefinition, instanceDefinitions);
-
- onDeploy(viewDefinition);
+ persistView(viewDefinition, instanceDefinitions);
- // update the registry with the view instances
- for (ViewInstanceEntity instanceEntity : instanceDefinitions) {
- addInstanceDefinition(viewDefinition, instanceEntity);
- handlerList.addViewInstance(instanceEntity);
- }
setViewStatus(viewDefinition, ViewEntity.ViewStatus.DEPLOYED, "Deployed " + extractedArchiveDirPath + ".");
} catch (Exception e) {
@@ -1322,6 +1337,21 @@ public class ViewRegistry {
}
}
+ // persist the given view and its instances
+ @Transactional
+ private void persistView(ViewEntity viewDefinition, Set<ViewInstanceEntity> instanceDefinitions) throws Exception {
+ // ensure that the view entity matches the db
+ syncView(viewDefinition, instanceDefinitions);
+
+ onDeploy(viewDefinition);
+
+ // update the registry with the view instances
+ for (ViewInstanceEntity instanceEntity : instanceDefinitions) {
+ addInstanceDefinition(viewDefinition, instanceEntity);
+ handlerList.addViewInstance(instanceEntity);
+ }
+ }
+
// extract the view archive for the given path.
protected static boolean extractViewArchive(String archivePath,
ViewExtractor extractor,
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/ViewConfig.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/ViewConfig.java b/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/ViewConfig.java
index 3a23ea7..ea04a21 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/ViewConfig.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/configuration/ViewConfig.java
@@ -19,6 +19,7 @@
package org.apache.ambari.server.view.configuration;
import org.apache.ambari.server.view.DefaultMasker;
+import org.apache.ambari.view.validation.Validator;
import org.apache.ambari.view.Masker;
import org.apache.ambari.view.View;
import org.apache.commons.lang.StringUtils;
@@ -84,6 +85,17 @@ public class ViewConfig {
private Class<? extends View> viewClass = null;
/**
+ * The main view class name.
+ */
+ @XmlElement(name="validator-class")
+ private String validator;
+
+ /**
+ * The view validator class.
+ */
+ private Class<? extends Validator> validatorClass = null;
+
+ /**
* The masker class name for parameters.
*/
@XmlElement(name="masker-class")
@@ -213,6 +225,31 @@ public class ViewConfig {
}
/**
+ * Get the view validator class name.
+ *
+ * @return the view validator class name
+ */
+ public String getValidator() {
+ return validator;
+ }
+
+ /**
+ * Get the view validator class.
+ *
+ * @param cl the class loader
+ *
+ * @return the view validator class
+ *
+ * @throws ClassNotFoundException if the class can not be loaded
+ */
+ public Class<? extends Validator> getValidatorClass(ClassLoader cl) throws ClassNotFoundException {
+ if (validatorClass == null) {
+ validatorClass = cl.loadClass(validator).asSubclass(Validator.class);
+ }
+ return validatorClass;
+ }
+
+ /**
* Get the masker class name.
* @return the masker class name
*/
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/main/java/org/apache/ambari/server/view/validation/InstanceValidationResultImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/validation/InstanceValidationResultImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/validation/InstanceValidationResultImpl.java
new file mode 100644
index 0000000..c536ae5
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/validation/InstanceValidationResultImpl.java
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.view.validation;
+
+import com.google.gson.Gson;
+import org.apache.ambari.view.validation.ValidationResult;
+
+import java.util.Map;
+
+/**
+ * View instance validation result. This result includes the validation results
+ * for the associated view instance and its properties.
+ */
+public class InstanceValidationResultImpl extends ValidationResultImpl {
+
+ /**
+ * Static Gson instance.
+ */
+ private static final Gson GSON = new Gson();
+
+ /**
+ * The validation results for the associated instance's properties
+ */
+ private final Map<String, ValidationResult> propertyResults;
+
+
+ // ----- Constructors ------------------------------------------------------
+
+ /**
+ * Construct an instance validation result.
+ *
+ * @param instanceResult the results of the instance validation
+ * @param propertyResults the results of the property validations
+ */
+ public InstanceValidationResultImpl(ValidationResult instanceResult, Map<String, ValidationResult> propertyResults) {
+ super(isValid(instanceResult, propertyResults), getDetail(instanceResult, propertyResults));
+ this.propertyResults = propertyResults;
+ }
+
+
+ // ----- InstanceValidationResultImpl --------------------------------------
+
+ /**
+ * Get the validation results for the properties of the associated instance.
+ *
+ * @return the property validation results
+ */
+ public Map<String, ValidationResult> getPropertyResults() {
+ return propertyResults;
+ }
+
+ /**
+ * Return this result as a JSON string.
+ *
+ * @return this result in JSON format
+ */
+ public String toJson() {
+ return GSON.toJson(this);
+ }
+
+
+ // ----- helper methods ----------------------------------------------------
+
+ // determine whether or not the given instance and property results are valid
+ private static boolean isValid(ValidationResult instanceResult, Map<String, ValidationResult> propertyResults) {
+ boolean instanceValid = instanceResult.isValid();
+ if (instanceValid) {
+ for (Map.Entry<String, ValidationResult> entry : propertyResults.entrySet()) {
+ ValidationResult propertyResult = entry.getValue();
+ if (propertyResult != null && !propertyResult.isValid()) {
+ return false;
+ }
+ }
+ }
+ return instanceValid;
+ }
+
+ // get a detail message from the given instance and property results
+ private static String getDetail(ValidationResult instanceResult, Map<String, ValidationResult> propertyResults) {
+ if (instanceResult.isValid()) {
+ for (Map.Entry<String, ValidationResult> entry : propertyResults.entrySet()) {
+ ValidationResult propertyResult = entry.getValue();
+ if (propertyResult != null && !propertyResult.isValid()) {
+ return "The instance has invalid properties.";
+ }
+ }
+ }
+ return instanceResult.getDetail();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/main/java/org/apache/ambari/server/view/validation/ValidationResultImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/validation/ValidationResultImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/validation/ValidationResultImpl.java
new file mode 100644
index 0000000..643eb1b
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/validation/ValidationResultImpl.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.view.validation;
+
+import org.apache.ambari.view.validation.ValidationResult;
+
+/**
+ * Simple validation result implementation.
+ */
+public class ValidationResultImpl implements ValidationResult{
+ /**
+ * Indicates whether or not the result is valid.
+ */
+ private final boolean valid;
+
+ /**
+ * Detail message for the result.
+ */
+ private final String detail;
+
+
+ // ----- Constructors ------------------------------------------------------
+
+ /**
+ * Construct a validation result.
+ *
+ * @param valid indicates whether or not the result is valid
+ * @param detail detail message for the result
+ */
+ public ValidationResultImpl(boolean valid, String detail) {
+ this.valid = valid;
+ this.detail = detail;
+ }
+
+
+ // ----- ValidationResult --------------------------------------------------
+
+ @Override
+ public boolean isValid() {
+ return valid;
+ }
+
+ @Override
+ public String getDetail() {
+ return detail;
+ }
+
+
+ // ----- helper methods ----------------------------------------------------
+
+ /**
+ * Factory method to create a validation result from an existing result (accounts for null).
+ *
+ * @param result the validation result; may be null
+ *
+ * @return a new validation result
+ */
+ public static ValidationResult create(ValidationResult result) {
+ if (result == null) {
+ result = SUCCESS;
+ }
+ return new ValidationResultImpl(result.isValid(), result.getDetail());
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseProviderTest.java
index fc8acd0..380142e 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseProviderTest.java
@@ -171,6 +171,34 @@ public class BaseProviderTest {
}
@Test
+ public void testIsPropertyRequested() {
+ Set<String> propertyIds = new HashSet<String>();
+ propertyIds.add("p1");
+ propertyIds.add("foo");
+ propertyIds.add("cat1/foo");
+ propertyIds.add("cat2/bar");
+ propertyIds.add("cat2/baz");
+ propertyIds.add("cat3/sub1/bam");
+ propertyIds.add("cat4/sub2/sub3/bat");
+ propertyIds.add("cat5/sub5");
+
+ assertTrue(BaseProvider.isPropertyRequested("foo", propertyIds));
+
+ assertTrue(BaseProvider.isPropertyRequested("cat2", propertyIds));
+
+ assertTrue(BaseProvider.isPropertyRequested("cat2/bar", propertyIds));
+
+ assertFalse(BaseProvider.isPropertyRequested("unsupported", propertyIds));
+
+ // we should allow anything under the category cat5/sub5
+ assertTrue(BaseProvider.isPropertyRequested("cat5/sub5/prop5", propertyIds));
+ assertTrue(BaseProvider.isPropertyRequested("cat5/sub5/sub5a/prop5a", propertyIds));
+
+ // we shouldn't allow anything under the category cat5/sub7
+ assertFalse(BaseProvider.isPropertyRequested("cat5/sub7/unsupported", propertyIds));
+ }
+
+ @Test
public void testSetResourcePropertyWithMaps() {
Set<String> propertyIds = new HashSet<String>();
propertyIds.add("cat1/emptyMapProperty");
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityTest.java
index 965cebb..f93403d 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityTest.java
@@ -26,6 +26,9 @@ import org.apache.ambari.server.view.configuration.ResourceConfigTest;
import org.apache.ambari.server.view.configuration.ViewConfig;
import org.apache.ambari.server.view.configuration.ViewConfigTest;
import org.apache.ambari.view.ViewDefinition;
+import org.apache.ambari.view.ViewInstanceDefinition;
+import org.apache.ambari.view.validation.ValidationResult;
+import org.apache.ambari.view.validation.Validator;
import org.junit.Assert;
import org.junit.Test;
@@ -268,6 +271,16 @@ public class ViewEntityTest {
}
@Test
+ public void testGetSetValidator() throws Exception {
+ ViewEntity viewDefinition = getViewEntity();
+
+ Validator validator = new TestValidator();
+
+ viewDefinition.setValidator(validator);
+ Assert.assertEquals(validator, viewDefinition.getValidator());
+ }
+
+ @Test
public void testisDeployed() throws Exception {
ViewEntity viewDefinition = getViewEntity();
@@ -294,4 +307,18 @@ public class ViewEntityTest {
viewDefinition.setSystem(true);
Assert.assertTrue(viewDefinition.isSystem());
}
+
+ public static class TestValidator implements Validator {
+ ValidationResult result;
+
+ @Override
+ public ValidationResult validateInstance(ViewInstanceDefinition definition, ValidationContext mode) {
+ return result;
+ }
+
+ @Override
+ public ValidationResult validateProperty(String property, ViewInstanceDefinition definition, ValidationContext mode) {
+ return result;
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewInstanceEntityTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewInstanceEntityTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewInstanceEntityTest.java
index 08aea6e..3aa17d1 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewInstanceEntityTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewInstanceEntityTest.java
@@ -27,7 +27,11 @@ import org.apache.ambari.server.view.configuration.InstanceConfig;
import org.apache.ambari.server.view.configuration.InstanceConfigTest;
import org.apache.ambari.server.view.configuration.ViewConfig;
import org.apache.ambari.server.view.configuration.ViewConfigTest;
+import org.apache.ambari.server.view.validation.InstanceValidationResultImpl;
+import org.apache.ambari.server.view.validation.ValidationResultImpl;
import org.apache.ambari.view.ResourceProvider;
+import org.apache.ambari.view.validation.ValidationResult;
+import org.apache.ambari.view.validation.Validator;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.security.core.GrantedAuthority;
@@ -405,7 +409,26 @@ public class ViewInstanceEntityTest {
ViewEntity viewEntity = ViewRegistryTest.getViewEntity(config, ambariConfig, getClass().getClassLoader(), "");
ViewInstanceEntity viewInstanceEntity = ViewRegistryTest.getViewInstanceEntity(viewEntity, config.getInstances().get(0));
- viewInstanceEntity.validate(viewEntity);
+ viewInstanceEntity.validate(viewEntity, Validator.ValidationContext.PRE_CREATE);
+ }
+
+ @Test
+ public void testValidateWithValidator() throws Exception {
+
+ Properties properties = new Properties();
+ properties.put("p1", "v1");
+
+ Configuration ambariConfig = new Configuration(properties);
+
+ ViewConfig config = ViewConfigTest.getConfig(xml_valid_instance);
+ ViewEntity viewEntity = ViewRegistryTest.getViewEntity(config, ambariConfig, getClass().getClassLoader(), "");
+ ViewInstanceEntity viewInstanceEntity = ViewRegistryTest.getViewInstanceEntity(viewEntity, config.getInstances().get(0));
+
+ ViewEntityTest.TestValidator validator = new ViewEntityTest.TestValidator();
+ validator.result = new ValidationResultImpl(true, "detail");
+ viewEntity.setValidator(validator);
+
+ viewInstanceEntity.validate(viewEntity, Validator.ValidationContext.PRE_CREATE);
}
@Test
@@ -421,13 +444,65 @@ public class ViewInstanceEntityTest {
ViewInstanceEntity viewInstanceEntity = ViewRegistryTest.getViewInstanceEntity(viewEntity, config.getInstances().get(0));
try {
- viewInstanceEntity.validate(viewEntity);
+ viewInstanceEntity.validate(viewEntity, Validator.ValidationContext.PRE_CREATE);
+ Assert.fail("Expected an IllegalStateException");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testValidateWithValidator_fail() throws Exception {
+
+ Properties properties = new Properties();
+ properties.put("p1", "v1");
+
+ Configuration ambariConfig = new Configuration(properties);
+
+ ViewConfig config = ViewConfigTest.getConfig(xml_invalid_instance);
+ ViewEntity viewEntity = ViewRegistryTest.getViewEntity(config, ambariConfig, getClass().getClassLoader(), "");
+ ViewInstanceEntity viewInstanceEntity = ViewRegistryTest.getViewInstanceEntity(viewEntity, config.getInstances().get(0));
+
+ ViewEntityTest.TestValidator validator = new ViewEntityTest.TestValidator();
+ validator.result = new ValidationResultImpl(false, "detail");
+ viewEntity.setValidator(validator);
+
+ try {
+ viewInstanceEntity.validate(viewEntity, Validator.ValidationContext.PRE_CREATE);
Assert.fail("Expected an IllegalStateException");
} catch (IllegalStateException e) {
// expected
}
}
+ @Test
+ public void testGetValidationResult() throws Exception {
+
+ Properties properties = new Properties();
+ properties.put("p1", "v1");
+
+ Configuration ambariConfig = new Configuration(properties);
+
+ ViewConfig config = ViewConfigTest.getConfig(xml_valid_instance);
+ ViewEntity viewEntity = ViewRegistryTest.getViewEntity(config, ambariConfig, getClass().getClassLoader(), "");
+ ViewInstanceEntity viewInstanceEntity = ViewRegistryTest.getViewInstanceEntity(viewEntity, config.getInstances().get(0));
+
+ ViewEntityTest.TestValidator validator = new ViewEntityTest.TestValidator();
+ validator.result = new ValidationResultImpl(true, "detail");
+ viewEntity.setValidator(validator);
+
+ InstanceValidationResultImpl result = viewInstanceEntity.getValidationResult(viewEntity, Validator.ValidationContext.PRE_CREATE);
+
+ Map<String, ValidationResult> propertyResults = result.getPropertyResults();
+
+ junit.framework.Assert.assertEquals(2, propertyResults.size());
+ junit.framework.Assert.assertTrue(propertyResults.containsKey("p1"));
+ junit.framework.Assert.assertTrue(propertyResults.containsKey("p2"));
+
+ junit.framework.Assert.assertTrue(propertyResults.get("p1").isValid());
+ junit.framework.Assert.assertTrue(propertyResults.get("p2").isValid());
+ }
+
public static ViewInstanceEntity getViewInstanceEntity(SecurityHelper securityHelper)
throws Exception {
ViewInstanceEntity viewInstanceEntity = getViewInstanceEntity();
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/test/java/org/apache/ambari/server/view/configuration/ViewConfigTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/view/configuration/ViewConfigTest.java b/ambari-server/src/test/java/org/apache/ambari/server/view/configuration/ViewConfigTest.java
index f6ec403..04cbe2b 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/view/configuration/ViewConfigTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/view/configuration/ViewConfigTest.java
@@ -24,6 +24,9 @@ import org.apache.ambari.view.ResourceAlreadyExistsException;
import org.apache.ambari.view.ResourceProvider;
import org.apache.ambari.view.SystemException;
import org.apache.ambari.view.UnsupportedPropertyException;
+import org.apache.ambari.view.ViewInstanceDefinition;
+import org.apache.ambari.view.validation.ValidationResult;
+import org.apache.ambari.view.validation.Validator;
import org.junit.Assert;
import org.junit.Test;
@@ -50,6 +53,7 @@ public class ViewConfigTest {
" <system>true</system>\n" +
" <icon64>/this/is/the/icon/url/icon64.png</icon64>\n" +
" <icon>/this/is/the/icon/url/icon.png</icon>\n" +
+ " <validator-class>org.apache.ambari.server.view.configuration.ViewConfigTest$MyValidator</validator-class>" +
" <masker-class>org.apache.ambari.server.view.DefaultMasker</masker-class>" +
" <parameter>\n" +
" <name>p1</name>\n" +
@@ -169,6 +173,12 @@ public class ViewConfigTest {
}
@Test
+ public void testGetValidator() throws Exception {
+ ViewConfig config = getConfig();
+ Assert.assertEquals("org.apache.ambari.server.view.configuration.ViewConfigTest$MyValidator", config.getValidator());
+ }
+
+ @Test
public void testMasker() throws Exception {
ViewConfig config = getConfig();
Assert.assertEquals("org.apache.ambari.server.view.DefaultMasker", config.getMasker());
@@ -249,6 +259,20 @@ public class ViewConfigTest {
// nothing
}
+ public static class MyValidator implements Validator {
+ ValidationResult result;
+
+ @Override
+ public ValidationResult validateInstance(ViewInstanceDefinition definition, ValidationContext mode) {
+ return result;
+ }
+
+ @Override
+ public ValidationResult validateProperty(String property, ViewInstanceDefinition definition, ValidationContext mode) {
+ return result;
+ }
+ }
+
public static class MyResource {
private String id;
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/test/java/org/apache/ambari/server/view/validation/InstanceValidationResultImplTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/view/validation/InstanceValidationResultImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/view/validation/InstanceValidationResultImplTest.java
new file mode 100644
index 0000000..b375cd2
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/view/validation/InstanceValidationResultImplTest.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.view.validation;
+
+import junit.framework.Assert;
+import org.apache.ambari.view.validation.ValidationResult;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * InstanceValidationResultImpl tests.
+ */
+public class InstanceValidationResultImplTest {
+
+ @Test
+ public void testGetPropertyResults() throws Exception {
+ ValidationResult result = new ValidationResultImpl(true, "detail");
+ Map<String, ValidationResult> propertyResults = new HashMap<String, ValidationResult>();
+
+ propertyResults.put("foo", new ValidationResultImpl(true, "foo detail"));
+ propertyResults.put("bar", new ValidationResultImpl(false, "bar detail"));
+
+ InstanceValidationResultImpl instanceValidationResult = new InstanceValidationResultImpl(result, propertyResults);
+
+ propertyResults = instanceValidationResult.getPropertyResults();
+
+ Assert.assertEquals(2, propertyResults.size());
+ Assert.assertTrue(propertyResults.containsKey("foo"));
+ Assert.assertTrue(propertyResults.containsKey("bar"));
+
+ Assert.assertTrue(propertyResults.get("foo").isValid());
+ Assert.assertFalse(propertyResults.get("bar").isValid());
+ }
+
+ @Test
+ public void testToJson() throws Exception {
+ ValidationResult result = new ValidationResultImpl(true, "detail");
+ Map<String, ValidationResult> propertyResults = new HashMap<String, ValidationResult>();
+
+ propertyResults.put("foo", new ValidationResultImpl(true, "foo detail"));
+ propertyResults.put("bar", new ValidationResultImpl(false, "bar detail"));
+
+ InstanceValidationResultImpl instanceValidationResult = new InstanceValidationResultImpl(result, propertyResults);
+
+ Assert.assertEquals("{\"propertyResults\":{\"foo\":{\"valid\":true,\"detail\":\"foo detail\"},\"bar\":{\"valid\":false,\"detail\":\"bar detail\"}},\"valid\":false,\"detail\":\"The instance has invalid properties.\"}",
+ instanceValidationResult.toJson());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-server/src/test/java/org/apache/ambari/server/view/validation/ValidationResultImplTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/view/validation/ValidationResultImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/view/validation/ValidationResultImplTest.java
new file mode 100644
index 0000000..9ad201c
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/view/validation/ValidationResultImplTest.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.view.validation;
+
+import junit.framework.Assert;
+import org.apache.ambari.view.validation.ValidationResult;
+import org.junit.Test;
+
+/**
+ * ValidationResultImpl tests.
+ */
+public class ValidationResultImplTest {
+
+ @Test
+ public void testIsValid() throws Exception {
+ ValidationResult result = new ValidationResultImpl(true, "detail");
+ Assert.assertTrue(result.isValid());
+
+ result = new ValidationResultImpl(false, "detail");
+ Assert.assertFalse(result.isValid());
+ }
+
+ @Test
+ public void testGetDetail() throws Exception {
+ ValidationResult result = new ValidationResultImpl(true, "detail");
+ Assert.assertEquals("detail", result.getDetail());
+ }
+
+ @Test
+ public void testCreate() throws Exception {
+ ValidationResult result = ValidationResultImpl.create(new ValidationResultImpl(true, "is true"));
+ Assert.assertTrue(result.isValid());
+ Assert.assertEquals("is true", result.getDetail());
+
+ result = ValidationResultImpl.create(new ValidationResultImpl(false, "is false"));
+ Assert.assertFalse(result.isValid());
+ Assert.assertEquals("is false", result.getDetail());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-views/src/main/java/org/apache/ambari/view/validation/ValidationResult.java
----------------------------------------------------------------------
diff --git a/ambari-views/src/main/java/org/apache/ambari/view/validation/ValidationResult.java b/ambari-views/src/main/java/org/apache/ambari/view/validation/ValidationResult.java
new file mode 100644
index 0000000..85ea399
--- /dev/null
+++ b/ambari-views/src/main/java/org/apache/ambari/view/validation/ValidationResult.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.view.validation;
+
+/**
+ * Validation result.
+ */
+public interface ValidationResult {
+
+ /**
+ * Determine whether or not the result is valid.
+ *
+ * @return true if the result is valid
+ */
+ public boolean isValid();
+
+ /**
+ * Get the detail of the validation result.
+ *
+ * @return the validation result detail
+ */
+ public String getDetail();
+
+ /**
+ * Successful validation result.
+ */
+ public static final ValidationResult SUCCESS = new ValidationResult() {
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public String getDetail() {
+ return "OK";
+ }
+ };
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-views/src/main/java/org/apache/ambari/view/validation/Validator.java
----------------------------------------------------------------------
diff --git a/ambari-views/src/main/java/org/apache/ambari/view/validation/Validator.java b/ambari-views/src/main/java/org/apache/ambari/view/validation/Validator.java
new file mode 100644
index 0000000..ee029a8
--- /dev/null
+++ b/ambari-views/src/main/java/org/apache/ambari/view/validation/Validator.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.view.validation;
+
+import org.apache.ambari.view.ViewInstanceDefinition;
+
+/**
+ * Interface for custom view validation. The validator is used validate a view instance and
+ * its properties.
+ */
+public interface Validator {
+ /**
+ * Validate the given view instance definition. Return {@code null} to indicate that
+ * no validation was performed.
+ *
+ * @param definition the view instance definition
+ * @param mode the validation mode
+ *
+ * @return the instance validation result; may be {@code null}
+ */
+ public ValidationResult validateInstance(ViewInstanceDefinition definition, ValidationContext mode);
+
+ /**
+ * Validate a property of the given view instance definition. Return {@code null} to indicate that
+ * no validation was performed.
+ *
+ * @param property the property name
+ * @param definition the view instance definition
+ * @param mode the validation mode
+ *
+ * @return the instance validation result; may be {@code null}
+ */
+ public ValidationResult validateProperty(String property, ViewInstanceDefinition definition, ValidationContext mode);
+
+ /**
+ * The context in which a view instance validation check is performed.
+ */
+ public enum ValidationContext {
+ PRE_CREATE, // validation prior to creation
+ PRE_UPDATE, // validation prior to update
+ EXISTING // validation of an existing view instance
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e2757784/ambari-views/src/main/resources/view.xsd
----------------------------------------------------------------------
diff --git a/ambari-views/src/main/resources/view.xsd b/ambari-views/src/main/resources/view.xsd
index b95e4ec..a9e5b12 100644
--- a/ambari-views/src/main/resources/view.xsd
+++ b/ambari-views/src/main/resources/view.xsd
@@ -249,6 +249,11 @@
<xs:documentation>The View class to receive framework events.</xs:documentation>
</xs:annotation>
</xs:element>
+ <xs:element type="xs:string" name="validator-class" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>The Validator class to validate view instances and properties.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
<xs:element type="xs:string" name="masker-class" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>The Masker class for masking view parameters.</xs:documentation>