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()); + } +}