001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.databind.model; 028 029import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; 030import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine; 031import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline; 032import gov.nist.secauto.metaschema.core.model.IFlagContainerSupport; 033import gov.nist.secauto.metaschema.core.model.IModule; 034import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.InternalModelSource; 035import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained; 036import gov.nist.secauto.metaschema.core.util.ObjectUtils; 037import gov.nist.secauto.metaschema.databind.IBindingContext; 038import gov.nist.secauto.metaschema.databind.io.BindingException; 039import gov.nist.secauto.metaschema.databind.model.annotations.BoundField; 040import gov.nist.secauto.metaschema.databind.model.annotations.Ignore; 041import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField; 042import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaFieldValue; 043import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints; 044 045import java.util.Objects; 046 047import edu.umd.cs.findbugs.annotations.NonNull; 048import edu.umd.cs.findbugs.annotations.Nullable; 049import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 050import nl.talsmasoftware.lazy4j.Lazy; 051 052public class DefaultFieldClassBinding 053 extends AbstractClassBinding 054 implements IFieldClassBinding { 055 056 @NonNull 057 private final MetaschemaField metaschemaField; 058 private IBoundFieldValueInstance fieldValue; 059 private IBoundFlagInstance jsonValueKeyFlagInstance; 060 private final Lazy<ClassBindingFlagContainerSupport> flagContainer; 061 private final Lazy<IValueConstrained> constraints; 062 063 /** 064 * Create a new {@link IClassBinding} for a Java bean annotated with the 065 * {@link BoundField} annotation. 066 * 067 * @param clazz 068 * the Java bean class 069 * @param bindingContext 070 * the Module binding environment context 071 * @return the Module field binding for the class 072 */ 073 @NonNull 074 public static DefaultFieldClassBinding createInstance( 075 @NonNull Class<?> clazz, 076 @NonNull IBindingContext bindingContext) { 077 Objects.requireNonNull(clazz, "clazz"); 078 if (!clazz.isAnnotationPresent(MetaschemaField.class)) { 079 throw new IllegalArgumentException( 080 String.format("Class '%s' is missing the '%s' annotation.", 081 clazz.getName(), 082 MetaschemaField.class.getName())); 083 } 084 return new DefaultFieldClassBinding(clazz, bindingContext); 085 } 086 087 /** 088 * Construct a new {@link IClassBinding} for a Java bean annotated with the 089 * {@link BoundField} annotation. 090 * 091 * @param clazz 092 * the Java bean class 093 * @param bindingContext 094 * the class binding context for which this class is participating 095 */ 096 protected DefaultFieldClassBinding( 097 @NonNull Class<?> clazz, 098 @NonNull IBindingContext bindingContext) { 099 super(clazz, bindingContext); 100 this.metaschemaField = ObjectUtils.notNull(clazz.getAnnotation(MetaschemaField.class)); 101 this.flagContainer = Lazy.lazy(() -> new ClassBindingFlagContainerSupport(this, this::handleFlagInstance)); 102 this.constraints = Lazy.lazy(() -> new ValueConstraintSupport( 103 clazz.getAnnotation(ValueConstraints.class), 104 InternalModelSource.instance())); 105 } 106 107 @SuppressWarnings("null") 108 @Override 109 public IFlagContainerSupport<IBoundFlagInstance> getFlagContainer() { 110 return flagContainer.get(); 111 } 112 113 @SuppressWarnings("null") 114 @Override 115 public IValueConstrained getConstraintSupport() { 116 return constraints.get(); 117 } 118 119 @NonNull 120 private MetaschemaField getMetaschemaFieldAnnotation() { 121 return metaschemaField; 122 } 123 124 @Override 125 public String getFormalName() { 126 return ModelUtil.resolveToString(getMetaschemaFieldAnnotation().formalName()); 127 } 128 129 @Override 130 public MarkupLine getDescription() { 131 return ModelUtil.resolveToMarkupLine(getMetaschemaFieldAnnotation().description()); 132 } 133 134 @Override 135 public @Nullable MarkupMultiline getRemarks() { 136 return ModelUtil.resolveToMarkupMultiline(getMetaschemaFieldAnnotation().description()); 137 } 138 139 @Override 140 public String getName() { 141 return getMetaschemaFieldAnnotation().name(); 142 } 143 144 @Override 145 public Object getDefaultValue() { 146 return getFieldValueInstance().getDefaultValue(); 147 } 148 149 /** 150 * Collect all fields that are part of the model for this class. 151 * 152 * @param clazz 153 * the class 154 * @return the field value instances if found or {@code null} otherwise 155 */ 156 protected java.lang.reflect.Field getFieldValueField(Class<?> clazz) { 157 java.lang.reflect.Field[] fields = clazz.getDeclaredFields(); 158 159 java.lang.reflect.Field retval = null; 160 161 Class<?> superClass = clazz.getSuperclass(); 162 if (superClass != null) { 163 // get instances from superclass 164 retval = getFieldValueField(superClass); 165 } 166 167 if (retval == null) { 168 for (java.lang.reflect.Field field : fields) { 169 if (!field.isAnnotationPresent(MetaschemaFieldValue.class)) { 170 // skip fields that aren't a field or assembly instance 171 continue; 172 } 173 174 if (field.isAnnotationPresent(Ignore.class)) { 175 // skip this field, since it is ignored 176 continue; 177 } 178 retval = field; 179 } 180 } 181 return retval; 182 } 183 184 /** 185 * Initialize the flag instances for this class. 186 * 187 * @return the field value instance 188 */ 189 protected IBoundFieldValueInstance initalizeFieldValueInstance() { 190 synchronized (this) { 191 if (this.fieldValue == null) { 192 java.lang.reflect.Field field = getFieldValueField(getBoundClass()); 193 if (field == null) { 194 throw new IllegalArgumentException( 195 String.format("Class '%s' is missing the '%s' annotation on one of its fields.", 196 getBoundClass().getName(), 197 MetaschemaFieldValue.class.getName())); 198 } 199 200 this.fieldValue = new DefaultFieldValueProperty(this, field); 201 } 202 return this.fieldValue; 203 } 204 } 205 206 @Override 207 public boolean isInline() { 208 return false; 209 } 210 211 @Override 212 public IBoundFieldInstance getInlineInstance() { 213 return null; 214 } 215 216 @SuppressWarnings("null") 217 @Override 218 public IBoundFieldValueInstance getFieldValueInstance() { 219 return initalizeFieldValueInstance(); 220 } 221 222 @Override 223 public Object getFieldValue(@NonNull Object item) { 224 return ObjectUtils.requireNonNull(getFieldValueInstance().getValue(item)); 225 } 226 227 protected void handleFlagInstance(IBoundFlagInstance instance) { 228 if (instance.isJsonValueKey()) { 229 this.jsonValueKeyFlagInstance = instance; 230 } 231 } 232 233 @Override 234 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "access is restricted using interface") 235 public IBoundFlagInstance getJsonValueKeyFlagInstance() { 236 // lazy load flags 237 flagContainer.get(); 238 return jsonValueKeyFlagInstance; 239 } 240 241 @Override 242 public String getJsonValueKeyName() { 243 return getFieldValueInstance().getJsonValueKeyName(); 244 } 245 246 @Override 247 public IDataTypeAdapter<?> getJavaTypeAdapter() { 248 return getFieldValueInstance().getJavaTypeAdapter(); 249 } 250 251 @Override 252 protected void copyBoundObjectInternal(@NonNull Object fromInstance, @NonNull Object toInstance) 253 throws BindingException { 254 super.copyBoundObjectInternal(fromInstance, toInstance); 255 256 getFieldValueInstance().copyBoundObject(fromInstance, toInstance); 257 } 258 259 @Override 260 protected Class<? extends IModule> getModuleClass() { 261 return getMetaschemaFieldAnnotation().moduleClass(); 262 } 263}