View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.databind.model;
28  
29  import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
30  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
31  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
32  import gov.nist.secauto.metaschema.core.model.IFlagContainerSupport;
33  import gov.nist.secauto.metaschema.core.model.IModule;
34  import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.InternalModelSource;
35  import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained;
36  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
37  import gov.nist.secauto.metaschema.databind.IBindingContext;
38  import gov.nist.secauto.metaschema.databind.io.BindingException;
39  import gov.nist.secauto.metaschema.databind.model.annotations.BoundField;
40  import gov.nist.secauto.metaschema.databind.model.annotations.Ignore;
41  import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField;
42  import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaFieldValue;
43  import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints;
44  
45  import java.util.Objects;
46  
47  import edu.umd.cs.findbugs.annotations.NonNull;
48  import edu.umd.cs.findbugs.annotations.Nullable;
49  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
50  import nl.talsmasoftware.lazy4j.Lazy;
51  
52  public class DefaultFieldClassBinding
53      extends AbstractClassBinding
54      implements IFieldClassBinding {
55  
56    @NonNull
57    private final MetaschemaField metaschemaField;
58    private IBoundFieldValueInstance fieldValue;
59    private IBoundFlagInstance jsonValueKeyFlagInstance;
60    private final Lazy<ClassBindingFlagContainerSupport> flagContainer;
61    private final Lazy<IValueConstrained> constraints;
62  
63    /**
64     * Create a new {@link IClassBinding} for a Java bean annotated with the
65     * {@link BoundField} annotation.
66     *
67     * @param clazz
68     *          the Java bean class
69     * @param bindingContext
70     *          the Module binding environment context
71     * @return the Module field binding for the class
72     */
73    @NonNull
74    public static DefaultFieldClassBinding createInstance(
75        @NonNull Class<?> clazz,
76        @NonNull IBindingContext bindingContext) {
77      Objects.requireNonNull(clazz, "clazz");
78      if (!clazz.isAnnotationPresent(MetaschemaField.class)) {
79        throw new IllegalArgumentException(
80            String.format("Class '%s' is missing the '%s' annotation.",
81                clazz.getName(),
82                MetaschemaField.class.getName()));
83      }
84      return new DefaultFieldClassBinding(clazz, bindingContext);
85    }
86  
87    /**
88     * Construct a new {@link IClassBinding} for a Java bean annotated with the
89     * {@link BoundField} annotation.
90     *
91     * @param clazz
92     *          the Java bean class
93     * @param bindingContext
94     *          the class binding context for which this class is participating
95     */
96    protected DefaultFieldClassBinding(
97        @NonNull Class<?> clazz,
98        @NonNull IBindingContext bindingContext) {
99      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 }