This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata.git


The following commit(s) were added to refs/heads/develop by this push:
     new 4034c6a  AIRAVATA-3328 Call setters on defaulted fields when mapping 
with Dozer
4034c6a is described below

commit 4034c6a5bed3d327ff2738468681164ee3a961eb
Author: Marcus Christie <machris...@apache.org>
AuthorDate: Thu May 21 10:53:07 2020 -0400

    AIRAVATA-3328 Call setters on defaulted fields when mapping with Dozer
---
 .../registry/core/utils/CustomBeanFactory.java     | 102 +++++++++++++++++++++
 .../registry/core/utils/ObjectMapperSingleton.java |   2 +-
 .../src/main/resources/dozer_mapping.xml           |   5 +
 .../registry/core/utils/CustomBeanFactoryTest.java |  54 +++++++++++
 .../core/utils/ObjectMapperSingletonTest.java      |  66 +++++++++++++
 5 files changed, 228 insertions(+), 1 deletion(-)

diff --git 
a/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/utils/CustomBeanFactory.java
 
b/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/utils/CustomBeanFactory.java
new file mode 100644
index 0000000..153285a
--- /dev/null
+++ 
b/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/utils/CustomBeanFactory.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * 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.airavata.registry.core.utils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.thrift.TBase;
+import org.apache.thrift.TFieldIdEnum;
+import org.apache.thrift.TFieldRequirementType;
+import org.apache.thrift.meta_data.FieldMetaData;
+import org.dozer.BeanFactory;
+import org.dozer.util.MappingUtils;
+import org.dozer.util.ReflectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CustomBeanFactory implements BeanFactory {
+
+    private final static Logger logger = 
LoggerFactory.getLogger(CustomBeanFactory.class);
+
+    @Override
+    public Object createBean(Object source, Class<?> sourceClass, String 
targetBeanId) {
+        Object result;
+        Class<?> destClass = MappingUtils.loadClass(targetBeanId);
+        if (logger.isDebugEnabled()) {
+            logger.debug("Creating bean of type " + destClass.getSimpleName());
+        }
+        result = ReflectionUtils.newInstance(destClass);
+        if (result instanceof TBase) {
+
+            callSettersOnThriftFieldsWithDefaults((TBase) result);
+        }
+        return result;
+    }
+
+    /**
+     * Thrift fields with default values aren't serialized and sent over the 
wire if
+     * their setters were never called. However, Dozer doesn't call the setter 
on
+     * the field of a target object when the target field's value already 
matches
+     * the source's field value. This results in the Thrift data model object 
field
+     * having the default value but it doesn't get serialized with that value 
(and
+     * for required fields validation fails). The following changes the 
semantics of
+     * defaulted Thrift fields a bit so that they are always "set" even if the
+     * source object had no such field, but this matches the more general 
semantics
+     * of what is expected from fields that have default values and it works 
around
+     * an annoyance with required default fields that would fail validation
+     * otherwise.
+     * 
+     * <p>
+     * See AIRAVATA-3268 and AIRAVATA-3328 for more information.
+     * 
+     * @param <T>
+     * @param <F>
+     * @param instance
+     */
+    private <T extends TBase<T, F>, F extends TFieldIdEnum> void 
callSettersOnThriftFieldsWithDefaults(
+            TBase<T, F> instance) {
+
+        try {
+            Field metaDataMapField = 
instance.getClass().getField("metaDataMap");
+            Map<F, FieldMetaData> metaDataMap = (Map<F, FieldMetaData>) 
metaDataMapField.get(null);
+            for (Entry<F, FieldMetaData> metaDataEntry : 
metaDataMap.entrySet()) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("processing field " + 
metaDataEntry.getValue().fieldName);
+                }
+                Object fieldValue = 
instance.getFieldValue(metaDataEntry.getKey());
+                if (fieldValue != null) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("field " + 
metaDataEntry.getValue().fieldName + " has a default value ["
+                                + fieldValue + "], calling setter to force the 
field to be set");
+                    }
+                    instance.setFieldValue(metaDataEntry.getKey(), fieldValue);
+                }
+            }
+        } catch (NoSuchFieldException | SecurityException | 
IllegalArgumentException | IllegalAccessException e) {
+            MappingUtils.throwMappingException(e);
+        }
+    }
+
+}
diff --git 
a/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/utils/ObjectMapperSingleton.java
 
b/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/utils/ObjectMapperSingleton.java
index ab5ffe9..16896e3 100644
--- 
a/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/utils/ObjectMapperSingleton.java
+++ 
b/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/utils/ObjectMapperSingleton.java
@@ -82,4 +82,4 @@ public class ObjectMapperSingleton extends DozerBeanMapper{
             return false;
         }
     }
-}
\ No newline at end of file
+}
diff --git 
a/modules/registry/registry-core/src/main/resources/dozer_mapping.xml 
b/modules/registry/registry-core/src/main/resources/dozer_mapping.xml
index 5d3e9b6..d9cc624 100644
--- a/modules/registry/registry-core/src/main/resources/dozer_mapping.xml
+++ b/modules/registry/registry-core/src/main/resources/dozer_mapping.xml
@@ -24,6 +24,11 @@
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
           xsi:schemaLocation="http://dozer.sourceforge.net
       http://dozer.sourceforge.net/schema/beanmapping.xsd";>
+
+    <configuration>
+        
<bean-factory>org.apache.airavata.registry.core.utils.CustomBeanFactory</bean-factory>
+    </configuration>
+
     <mapping>
         
<class-a>org.apache.airavata.model.appcatalog.storageresource.StorageResourceDescription</class-a>
         
<class-b>org.apache.airavata.registry.core.entities.appcatalog.StorageInterfaceEntity</class-b>
diff --git 
a/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/utils/CustomBeanFactoryTest.java
 
b/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/utils/CustomBeanFactoryTest.java
new file mode 100644
index 0000000..27cd5b9
--- /dev/null
+++ 
b/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/utils/CustomBeanFactoryTest.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.airavata.registry.core.utils;
+
+import org.apache.airavata.model.experiment.UserConfigurationDataModel;
+import org.apache.thrift.TFieldRequirementType;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CustomBeanFactoryTest {
+    
+    @Test
+    public void testRequiredFieldWithDefault() {
+        Assert.assertEquals(TFieldRequirementType.REQUIRED, 
UserConfigurationDataModel.metaDataMap.get(UserConfigurationDataModel._Fields.AIRAVATA_AUTO_SCHEDULE).requirementType);
+        UserConfigurationDataModel fromConstructor = new 
UserConfigurationDataModel();
+        Assert.assertFalse(fromConstructor.isSetAiravataAutoSchedule());
+
+        CustomBeanFactory customBeanFactory = new CustomBeanFactory();
+        UserConfigurationDataModel fromFactory = (UserConfigurationDataModel) 
customBeanFactory
+                .createBean(null, null, 
UserConfigurationDataModel.class.getName());
+        Assert.assertTrue(fromFactory.isSetAiravataAutoSchedule());
+    }
+
+    @Test
+    public void testOptionalFieldWithDefault() {
+        Assert.assertEquals(TFieldRequirementType.OPTIONAL, 
UserConfigurationDataModel.metaDataMap.get(UserConfigurationDataModel._Fields.SHARE_EXPERIMENT_PUBLICLY).requirementType);
+        UserConfigurationDataModel fromConstructor = new 
UserConfigurationDataModel();
+        Assert.assertFalse(fromConstructor.isSetShareExperimentPublicly());
+
+        CustomBeanFactory customBeanFactory = new CustomBeanFactory();
+        UserConfigurationDataModel fromFactory = (UserConfigurationDataModel) 
customBeanFactory
+                .createBean(null, null, 
UserConfigurationDataModel.class.getName());
+        Assert.assertTrue(fromFactory.isSetShareExperimentPublicly());
+    }
+}
diff --git 
a/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/utils/ObjectMapperSingletonTest.java
 
b/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/utils/ObjectMapperSingletonTest.java
new file mode 100644
index 0000000..9d29b84
--- /dev/null
+++ 
b/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/utils/ObjectMapperSingletonTest.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.airavata.registry.core.utils;
+
+import org.apache.airavata.model.experiment.UserConfigurationDataModel;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ObjectMapperSingletonTest {
+
+    public static class TestUserConfigurationDataModel {
+        private boolean airavataAutoSchedule;
+
+        public boolean isAiravataAutoSchedule() {
+            return airavataAutoSchedule;
+        }
+
+        public void setAiravataAutoSchedule(boolean airavataAutoSchedule) {
+            this.airavataAutoSchedule = airavataAutoSchedule;
+        }
+    }
+
+    @Test
+    public void testCopyBooleanFieldsWithDefaultValue() {
+
+        TestUserConfigurationDataModel testUserConfigurationDataModel = new 
TestUserConfigurationDataModel();
+        testUserConfigurationDataModel.setAiravataAutoSchedule(false);
+
+        // Make sure these fields have default values
+        Assert.assertNotNull("airavataAutoSchedule has default value", new 
UserConfigurationDataModel().getFieldValue(UserConfigurationDataModel._Fields.AIRAVATA_AUTO_SCHEDULE));
+        Assert.assertNotNull("overrideManualScheduledParams has default 
value", new 
UserConfigurationDataModel().getFieldValue(UserConfigurationDataModel._Fields.OVERRIDE_MANUAL_SCHEDULED_PARAMS));
+        Assert.assertNotNull("shareExperimentPublicly has default value", new 
UserConfigurationDataModel().getFieldValue(UserConfigurationDataModel._Fields.SHARE_EXPERIMENT_PUBLICLY));
+        UserConfigurationDataModel userConfigurationDataModel = 
ObjectMapperSingleton.getInstance()
+                .map(testUserConfigurationDataModel, 
UserConfigurationDataModel.class);
+
+        
Assert.assertTrue(userConfigurationDataModel.isSetAiravataAutoSchedule());
+        
Assert.assertFalse(userConfigurationDataModel.isAiravataAutoSchedule());
+        Assert.assertTrue(
+                "even though overrideManualScheduledParams isn't a field on 
the source object, since it has a default value it should be set",
+                
userConfigurationDataModel.isSetOverrideManualScheduledParams());
+        
Assert.assertFalse(userConfigurationDataModel.isOverrideManualScheduledParams());
+        Assert.assertTrue(
+                "even though shareExperimentPublicly isn't a field on the 
source object, since it has a default value it should be set",
+                userConfigurationDataModel.isSetShareExperimentPublicly());
+        
Assert.assertFalse(userConfigurationDataModel.isShareExperimentPublicly());
+    }
+}

Reply via email to