Upgrade SDC from Titan to Janus Graph
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / impl / PropertyBusinessLogic.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.sdc.be.components.impl;
22
23 import com.google.gson.JsonElement;
24 import fj.data.Either;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.Optional;
30 import java.util.function.Supplier;
31 import javax.servlet.ServletContext;
32 import org.apache.commons.collections.CollectionUtils;
33 import org.apache.commons.collections.MapUtils;
34 import org.apache.commons.lang3.tuple.ImmutablePair;
35 import org.openecomp.sdc.be.config.BeEcompErrorManager;
36 import org.openecomp.sdc.be.dao.api.ActionStatus;
37 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
38 import org.openecomp.sdc.be.datatypes.elements.ListDataDefinition;
39 import org.openecomp.sdc.be.datatypes.elements.OperationDataDefinition;
40 import org.openecomp.sdc.be.datatypes.elements.OperationInputDefinition;
41 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
42 import org.openecomp.sdc.be.datatypes.elements.SchemaDefinition;
43 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
44 import org.openecomp.sdc.be.impl.WebAppContextWrapper;
45 import org.openecomp.sdc.be.model.Component;
46 import org.openecomp.sdc.be.model.ComponentInstanceInterface;
47 import org.openecomp.sdc.be.model.ComponentParametersView;
48 import org.openecomp.sdc.be.model.DataTypeDefinition;
49 import org.openecomp.sdc.be.model.IComplexDefaultValue;
50 import org.openecomp.sdc.be.model.InterfaceDefinition;
51 import org.openecomp.sdc.be.model.PropertyDefinition;
52 import org.openecomp.sdc.be.model.operations.api.IElementOperation;
53 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
54 import org.openecomp.sdc.be.model.operations.utils.ComponentValidationUtils;
55 import org.openecomp.sdc.be.model.tosca.ToscaPropertyType;
56 import org.openecomp.sdc.be.model.tosca.converters.PropertyValueConverter;
57 import org.openecomp.sdc.be.model.tosca.validators.PropertyTypeValidator;
58 import org.openecomp.sdc.be.resources.data.EntryData;
59 import org.openecomp.sdc.common.api.Constants;
60 import org.openecomp.sdc.common.log.wrappers.Logger;
61 import org.openecomp.sdc.exception.ResponseFormat;
62 import org.springframework.web.context.WebApplicationContext;
63
64 @org.springframework.stereotype.Component("propertyBusinessLogic")
65 public class PropertyBusinessLogic extends BaseBusinessLogic {
66
67     private static final String CREATE_PROPERTY = "CreateProperty";
68
69     private static final Logger log = Logger.getLogger(PropertyBusinessLogic.class);
70
71     private static final String EMPTY_VALUE = null;
72
73     protected static IElementOperation getElementDao(Class<IElementOperation> class1, ServletContext context) {
74         WebAppContextWrapper webApplicationContextWrapper = (WebAppContextWrapper) context.getAttribute(Constants.WEB_APPLICATION_CONTEXT_WRAPPER_ATTR);
75
76         WebApplicationContext webApplicationContext = webApplicationContextWrapper.getWebAppContext(context);
77
78         return webApplicationContext.getBean(class1);
79     }
80
81     public Either<Map<String, DataTypeDefinition>, ResponseFormat> getAllDataTypes() {
82         return getAllDataTypes(applicationDataTypeCache);
83     }
84
85     /**
86      * Create new property on component in graph
87      *
88      * @param componentId
89      * @param propertyName
90      * @param newPropertyDefinition
91      * @param userId
92      * @return either properties or response format
93      */
94
95     public Either<EntryData<String, PropertyDefinition>, ResponseFormat> addPropertyToComponent(String componentId,
96                                                                                                 String propertyName,
97                                                                                                 PropertyDefinition newPropertyDefinition,
98                                                                                                 String userId) {
99         Either<EntryData<String, PropertyDefinition>, ResponseFormat> result = null;
100
101         validateUserExists(userId, "create Property", false);
102
103         Either<Component, StorageOperationStatus> serviceElement =
104                 toscaOperationFacade.getToscaElement(componentId);
105         if (serviceElement.isRight()) {
106             result = Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
107             return result;
108         }
109         Component component = serviceElement.left().value();
110         NodeTypeEnum nodeType = component.getComponentType().getNodeType();
111         StorageOperationStatus lockResult = graphLockOperation.lockComponent(componentId, nodeType );
112         if (!lockResult.equals(StorageOperationStatus.OK)) {
113             BeEcompErrorManager.getInstance().logBeFailedLockObjectError(CREATE_PROPERTY, nodeType.name().toLowerCase(), componentId);
114             log.info("Failed to lock component {}. Error - {}", componentId, lockResult);
115             result = Either.right(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR));
116             return result;
117         }
118
119         try {
120             if (!ComponentValidationUtils.canWorkOnComponent(component, userId)) {
121                 result = Either.right(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION));
122                 return result;
123             }
124
125             List<PropertyDefinition> properties = component.getProperties();
126
127             if(CollectionUtils.isEmpty(properties)) {
128                 properties = new ArrayList<>();
129             }
130
131             if(isPropertyExistInComponent(properties, propertyName)) {
132
133                 result =
134                     Either.right(componentsUtils.getResponseFormat(ActionStatus
135                         .PROPERTY_ALREADY_EXIST, propertyName));
136                 return result;
137
138             } else {
139
140                 Either<Map<String, DataTypeDefinition>, ResponseFormat> allDataTypes = getAllDataTypes(applicationDataTypeCache);
141                 if (allDataTypes.isRight()) {
142                     result = Either.right(allDataTypes.right().value());
143                     return result;
144                 }
145
146                 Map<String, DataTypeDefinition> dataTypes = allDataTypes.left().value();
147
148                 // validate property default values
149                 Either<Boolean, ResponseFormat> defaultValuesValidation = validatePropertyDefaultValue(newPropertyDefinition, dataTypes);
150                 if (defaultValuesValidation.isRight()) {
151                     result = Either.right(defaultValuesValidation.right().value());
152                     return result;
153                 }
154                 // convert property
155                 ToscaPropertyType type = getType(newPropertyDefinition.getType());
156                 if (type != null) {
157                     PropertyValueConverter converter = type.getConverter();
158                     // get inner type
159                     String innerType = null;
160                     if (newPropertyDefinition != null) {
161                         SchemaDefinition schema = newPropertyDefinition.getSchema();
162                         if (schema != null) {
163                             PropertyDataDefinition prop = schema.getProperty();
164                             if (prop != null) {
165                                 innerType = prop.getType();
166                             }
167                         }
168                         String convertedValue = null;
169                         if (newPropertyDefinition.getDefaultValue() != null) {
170                             convertedValue = converter.convert(
171                                 newPropertyDefinition.getDefaultValue(), innerType, allDataTypes.left().value());
172                             newPropertyDefinition.setDefaultValue(convertedValue);
173                         }
174                     }
175                 }
176                 Either<PropertyDefinition, StorageOperationStatus> addPropertyEither =
177                     toscaOperationFacade
178                         .addPropertyToComponent(propertyName, newPropertyDefinition, component);
179
180                 if (addPropertyEither.isRight()) {
181                     log.info("Failed to add new property {}. Error - {}", componentId,
182                         addPropertyEither.right().value());
183                     result = Either.right(componentsUtils.getResponseFormat(ActionStatus
184                         .GENERAL_ERROR));
185                     return result;
186                 }
187             }
188
189             result = Either.left(new EntryData<>(propertyName, newPropertyDefinition));
190             return result;
191
192         } finally {
193             commitOrRollback(result);
194             // unlock component
195             graphLockOperation.unlockComponent(componentId, nodeType);
196         }
197
198     }
199
200     /**
201      * Get property of component
202      *
203      * @param componentId
204      * @param propertyId
205      * @param userId
206      * @return either properties or response format
207      */
208
209     public Either<Map.Entry<String, PropertyDefinition>, ResponseFormat> getComponentProperty(String componentId, String propertyId, String userId) {
210
211         validateUserExists(userId, "create Component Instance", false);
212         // Get the resource from DB
213         Either<Component, StorageOperationStatus> status =
214             toscaOperationFacade.getToscaElement(componentId);
215         if (status.isRight()) {
216             return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
217         }
218         Component component = status.left().value();
219         List<PropertyDefinition> properties = component.getProperties();
220         if(CollectionUtils.isEmpty(properties)) {
221             return Either.right(componentsUtils.getResponseFormat(ActionStatus.PROPERTY_NOT_FOUND, ""));
222         }
223
224         for(PropertyDefinition property : properties) {
225             if(property.getUniqueId().equals(propertyId)) {
226                 return Either.left(new EntryData<>(property.getName(), property));
227             }
228         }
229         return Either.right(componentsUtils.getResponseFormat(ActionStatus.PROPERTY_NOT_FOUND, ""));
230     }
231
232
233     public Either<List<PropertyDefinition>, ResponseFormat> getPropertiesList(String componentId,
234                                                                               String userId) {
235         validateUserExists(userId, "create Component Instance", false);
236
237         // Get the resource from DB
238         ComponentParametersView filter = new ComponentParametersView(true);
239         filter.setIgnoreProperties(false);
240         Either<Component, StorageOperationStatus> status =
241             toscaOperationFacade.getToscaElement(componentId);
242         if (status.isRight()) {
243             return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
244         }
245         Component component = status.left().value();
246         List<PropertyDefinition> properties = component.getProperties();
247
248         return Either.left(properties);
249     }
250
251
252     /**
253      * delete property of component from graph
254      *
255      * @param componentId
256      * @param propertyId
257      * @param userId
258      * @return either properties or response format
259      */
260
261     public Either<Map.Entry<String, PropertyDefinition>, ResponseFormat> deletePropertyFromComponent(String componentId, String propertyId, String userId) {
262
263         Either<Map.Entry<String, PropertyDefinition>, ResponseFormat> result = null;
264
265         validateUserExists(userId, "delete Property", false);
266
267         // Get the resource from DB
268         Either<Component, StorageOperationStatus> getComponentRes = toscaOperationFacade.getToscaElement(componentId);
269         if (getComponentRes.isRight()) {
270             result = Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
271             return result;
272         }
273         Component component = getComponentRes.left().value();
274         NodeTypeEnum nodeType = component.getComponentType().getNodeType();
275         StorageOperationStatus lockResult = graphLockOperation.lockComponent(componentId, nodeType);
276         if (!lockResult.equals(StorageOperationStatus.OK)) {
277             BeEcompErrorManager.getInstance().logBeFailedLockObjectError(CREATE_PROPERTY, nodeType.name().toLowerCase(),
278                     componentId);
279             result = Either.right(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR));
280             return result;
281         }
282
283         try {
284             // verify that resource is checked-out and the user is the last
285             // updater
286             if (!ComponentValidationUtils.canWorkOnComponent(component, userId)) {
287                 result = Either.right(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION));
288                 return result;
289             }
290
291             // verify property exist in resource
292             Either<Map.Entry<String, PropertyDefinition>, ResponseFormat> statusGetProperty =
293                 getComponentProperty(componentId, propertyId, userId);
294             if (statusGetProperty.isRight()) {
295                 result = Either.right(statusGetProperty.right().value());
296                 return result;
297             }
298
299             Map.Entry<String, PropertyDefinition> propertyDefinitionEntry = statusGetProperty.left().value();
300
301             // verify that the property is not used by operation
302             if (isPropertyUsedByOperation(component, propertyDefinitionEntry.getValue())) {
303                 return Either.right(componentsUtils.getResponseFormat(ActionStatus
304                     .PROPERTY_USED_BY_OPERATION));
305             }
306
307             StorageOperationStatus status =
308                 toscaOperationFacade.deletePropertyOfComponent(component, propertyDefinitionEntry.getKey());
309             if (status != StorageOperationStatus.OK) {
310                 result = Either.right(componentsUtils.getResponseFormat(componentsUtils
311                     .convertFromStorageResponse(status), component.getName()));
312                 return result;
313             }
314             result = Either.left(propertyDefinitionEntry);
315             return result;
316
317         } finally {
318             commitOrRollback(result);
319             // unlock component
320             graphLockOperation.unlockComponent(componentId, nodeType);
321         }
322     }
323
324     public boolean isPropertyUsedByOperation(Component component,
325                                              PropertyDefinition propertyDefinitionEntry) {
326
327         // Component's own interfaces
328         Map<String, InterfaceDefinition> interfaces = component.getInterfaces();
329         if(MapUtils.isNotEmpty(interfaces)){
330           for(Map.Entry<String, InterfaceDefinition> interfaceEntry : interfaces.entrySet()) {
331             if (isPropertyExistInOperationInterface(propertyDefinitionEntry, interfaceEntry.getValue())) {
332               return true;
333             }
334           }
335         }
336
337         // Component's child's component interfaces
338         if(isPropertyUsedInCIInterfaces(component.getComponentInstancesInterfaces(), propertyDefinitionEntry)){
339             return true;
340         }
341
342         // Component's parent's component interfaces
343         Either<List<Component>, StorageOperationStatus> componentList = toscaOperationFacade.getParentComponents(component.getUniqueId());
344         if(componentList.isLeft()){
345             for (Component parentComponent : componentList.left().value()) {
346                 if(isPropertyUsedInCIInterfaces(parentComponent.getComponentInstancesInterfaces(), propertyDefinitionEntry)){
347                     return true;
348                 }
349             }
350         }
351
352         return false;
353     }
354
355     private boolean isPropertyUsedInCIInterfaces(Map<String, List<ComponentInstanceInterface>> componentInstanceInterfaces, PropertyDefinition propertyDefinitionEntry){
356         Optional<ComponentInstanceInterface> isPropertyExistInOperationInterface = Optional.empty();
357         if(MapUtils.isNotEmpty(componentInstanceInterfaces)){
358             isPropertyExistInOperationInterface = componentInstanceInterfaces.entrySet().stream()
359                     .flatMap(interfaceEntry -> interfaceEntry.getValue().stream())
360                     .filter(instanceInterface -> isPropertyExistInOperationInterface(propertyDefinitionEntry, instanceInterface))
361                     .findAny();
362         }
363         return isPropertyExistInOperationInterface.isPresent();
364     }
365
366     private boolean isPropertyExistInOperationInterface(PropertyDefinition propertyDefinition,
367                                                         InterfaceDefinition interfaceDefinition) {
368         Map<String, OperationDataDefinition> operations =
369             interfaceDefinition.getOperations();
370         for(Map.Entry<String, OperationDataDefinition> operationEntry : operations
371             .entrySet()) {
372             Optional<OperationInputDefinition> inputWithDeletedPropertyCandidate =
373                 getInputWithDeclaredProperty(propertyDefinition, operationEntry);
374
375             if(inputWithDeletedPropertyCandidate.isPresent()) {
376                 return true;
377             }
378         }
379         return false;
380     }
381
382     private Optional<OperationInputDefinition> getInputWithDeclaredProperty(PropertyDefinition propertyDefinition,
383                                                                             Map.Entry<String, OperationDataDefinition> operationEntry) {
384         ListDataDefinition<OperationInputDefinition> inputs =
385             operationEntry.getValue().getInputs();
386         List<OperationInputDefinition> operationInputsList =
387             Objects.isNull(inputs) ? null : inputs.getListToscaDataDefinition();
388
389         if(CollectionUtils.isEmpty(operationInputsList)) {
390             return Optional.empty();
391         }
392
393         return operationInputsList.stream().filter(input -> input.getInputId().equals(propertyDefinition.getUniqueId())
394                 || (input.getSourceProperty() != null && input.getSourceProperty().equals(propertyDefinition.getUniqueId()))).findAny();
395     }
396
397     /**
398      * update property
399      *
400      * @param componentId
401      * @param propertyId
402      * @param newPropertyDefinition
403      * @param userId
404      * @return either properties or response format
405      */
406
407     public Either<EntryData<String, PropertyDefinition>, ResponseFormat> updateComponentProperty(String componentId,
408                                                                         String propertyId,
409                                                                         PropertyDefinition newPropertyDefinition,
410                                                                         String userId) {
411
412         Either<EntryData<String, PropertyDefinition>, ResponseFormat> result = null;
413
414         Either<Component, StorageOperationStatus> status = toscaOperationFacade.getToscaElement(
415                 componentId);
416         if (status.isRight()) {
417             return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
418         }
419         Component component = status.left().value();
420         NodeTypeEnum nodeType = component.getComponentType().getNodeType();
421
422         if (!ComponentValidationUtils.canWorkOnComponent(component, userId)) {
423             return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION));
424         }
425
426         StorageOperationStatus lockResult = graphLockOperation.lockComponent(componentId, nodeType);
427         if (!lockResult.equals(StorageOperationStatus.OK)) {
428             BeEcompErrorManager.getInstance().logBeFailedLockObjectError(CREATE_PROPERTY, nodeType.name().toLowerCase(),
429                     componentId);
430             result = Either.right(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR));
431             return result;
432         }
433
434         try {
435             Either<Map.Entry<String, PropertyDefinition>, ResponseFormat> statusGetProperty =
436                 getComponentProperty(componentId, propertyId, userId);
437             if (statusGetProperty.isRight()) {
438                 result = Either.right(statusGetProperty.right().value());
439                 return result;
440             }
441             String propertyName = statusGetProperty.left().value().getKey();
442
443             Either<PropertyDefinition, StorageOperationStatus> either =
444                 toscaOperationFacade.updatePropertyOfComponent(component, newPropertyDefinition);
445             if (either.isRight()) {
446                 result = Either.right(componentsUtils.getResponseFormatByResource(componentsUtils.convertFromStorageResponse(either.right().value()), component.getName()));
447                 return result;
448             }
449
450             EntryData<String, PropertyDefinition> property = new EntryData<>(propertyName, either.left().value());
451             result = Either.left(property);
452             return result;
453
454         } finally {
455             commitOrRollback(result);
456             graphLockOperation.unlockComponent(componentId, nodeType);
457         }
458
459     }
460
461     private boolean isPropertyExistInComponent(List<PropertyDefinition> properties, String propertyName) {
462         if(CollectionUtils.isEmpty(properties)) {
463             return false;
464         }
465
466         Optional<PropertyDefinition> propertyCandidate =
467             properties.stream().filter(property -> property.getName().equals(propertyName))
468                 .findAny();
469
470         return propertyCandidate.isPresent();
471     }
472
473     private StorageOperationStatus validateAndUpdateProperty(IComplexDefaultValue propertyDefinition, Map<String, DataTypeDefinition> dataTypes) {
474
475         log.trace("Going to validate property type and value. {}", propertyDefinition);
476
477         String propertyType = propertyDefinition.getType();
478         String value = propertyDefinition.getDefaultValue();
479
480         ToscaPropertyType type = getType(propertyType);
481
482         if (type == null) {
483             DataTypeDefinition dataTypeDefinition = dataTypes.get(propertyType);
484             if (dataTypeDefinition == null) {
485                 log.debug("The type {} of property cannot be found.", propertyType);
486                 return StorageOperationStatus.INVALID_TYPE;
487             }
488             return validateAndUpdateComplexValue(propertyDefinition, propertyType, value, dataTypeDefinition, dataTypes);
489         }
490         String innerType;
491
492         Either<String, JanusGraphOperationStatus> checkInnerType = getInnerType(type, propertyDefinition::getSchema);
493         if (checkInnerType.isRight()) {
494             return StorageOperationStatus.INVALID_TYPE;
495         }
496         innerType = checkInnerType.left().value();
497
498         log.trace("After validating property type {}", propertyType);
499
500         boolean isValidProperty = isValidValue(type, value, innerType, dataTypes);
501         if (!isValidProperty) {
502             log.info("The value {} of property from type {} is invalid", value, type);
503             return StorageOperationStatus.INVALID_VALUE;
504         }
505
506         PropertyValueConverter converter = type.getConverter();
507
508         if (isEmptyValue(value)) {
509             log.debug("Default value was not sent for property {}. Set default value to {}", propertyDefinition.getName(), EMPTY_VALUE);
510             propertyDefinition.setDefaultValue(EMPTY_VALUE);
511         } else if (!isEmptyValue(value)) {
512             String convertedValue = converter.convert(value, innerType, dataTypes);
513             propertyDefinition.setDefaultValue(convertedValue);
514         }
515         return StorageOperationStatus.OK;
516     }
517
518     private StorageOperationStatus validateAndUpdateComplexValue(IComplexDefaultValue propertyDefinition, String propertyType,
519                                                                  String value, DataTypeDefinition dataTypeDefinition, Map<String, DataTypeDefinition> dataTypes) {
520
521         ImmutablePair<JsonElement, Boolean> validateResult = dataTypeValidatorConverter.validateAndUpdate(value, dataTypeDefinition, dataTypes);
522
523         if (validateResult.right) {
524             log.debug("The value {} of property from type {} is invalid", propertyType, propertyType);
525             return StorageOperationStatus.INVALID_VALUE;
526         }
527
528         JsonElement jsonElement = validateResult.left;
529
530         log.trace("Going to update value in property definition {} {}" , propertyDefinition.getName() , jsonElement);
531
532         updateValue(propertyDefinition, jsonElement);
533
534         return StorageOperationStatus.OK;
535     }
536
537     private void updateValue(IComplexDefaultValue propertyDefinition, JsonElement jsonElement) {
538
539         propertyDefinition.setDefaultValue(getValueFromJsonElement(jsonElement));
540
541     }
542
543     @Override
544     protected String getValueFromJsonElement(JsonElement jsonElement) {
545         if (jsonElement == null || jsonElement.isJsonNull()) {
546             return EMPTY_VALUE;
547         }
548         if(jsonElement.toString().isEmpty()){
549             return "";
550         }
551         return jsonElement.toString();
552     }
553
554     private Either<String, JanusGraphOperationStatus> getInnerType(ToscaPropertyType type, Supplier<SchemaDefinition> schemeGen) {
555         String innerType = null;
556         if (type == ToscaPropertyType.LIST || type == ToscaPropertyType.MAP) {
557
558             SchemaDefinition def = schemeGen.get();
559             if (def == null) {
560                 log.debug("Schema doesn't exists for property of type {}", type);
561                 return Either.right(JanusGraphOperationStatus.ILLEGAL_ARGUMENT);
562             }
563             PropertyDataDefinition propDef = def.getProperty();
564             if (propDef == null) {
565                 log.debug("Property in Schema Definition inside property of type {} doesn't exist", type);
566                 return Either.right(JanusGraphOperationStatus.ILLEGAL_ARGUMENT);
567             }
568             innerType = propDef.getType();
569         }
570         return Either.left(innerType);
571     }
572
573     @Override
574     protected boolean isValidValue(ToscaPropertyType type, String value, String innerType, Map<String, DataTypeDefinition> dataTypes) {
575         if (isEmptyValue(value)) {
576             return true;
577         }
578         PropertyTypeValidator validator = type.getValidator();
579         return validator.isValid(value, innerType, dataTypes);
580     }
581
582     @Override
583     public boolean isEmptyValue(String value) {
584         return value == null;
585     }
586 }