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.codegen;
28  
29  import static org.junit.jupiter.api.Assertions.assertAll;
30  import static org.junit.jupiter.api.Assertions.assertEquals;
31  
32  import gov.nist.secauto.metaschema.core.model.IModule;
33  import gov.nist.secauto.metaschema.core.model.MetaschemaException;
34  import gov.nist.secauto.metaschema.core.model.xml.ModuleLoader;
35  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
36  import gov.nist.secauto.metaschema.databind.IBindingContext;
37  import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration;
38  import gov.nist.secauto.metaschema.databind.io.BindingException;
39  import gov.nist.secauto.metaschema.databind.io.Format;
40  import gov.nist.secauto.metaschema.databind.io.IDeserializer;
41  
42  import org.apache.logging.log4j.LogManager;
43  import org.apache.logging.log4j.Logger;
44  import org.junit.jupiter.api.Assertions;
45  import org.junit.jupiter.api.Test;
46  import org.junit.jupiter.api.io.TempDir;
47  import org.junit.platform.commons.util.ReflectionUtils;
48  
49  import java.io.IOException;
50  import java.io.Writer;
51  import java.nio.file.Files;
52  import java.nio.file.Path;
53  import java.nio.file.Paths;
54  import java.nio.file.StandardOpenOption;
55  import java.util.Iterator;
56  import java.util.List;
57  import java.util.Map;
58  import java.util.Set;
59  
60  import edu.umd.cs.findbugs.annotations.NonNull;
61  import edu.umd.cs.findbugs.annotations.Nullable;
62  
63  class BasicMetaschemaTest {
64    private static final ModuleLoader LOADER = new ModuleLoader();
65    private static final Logger LOGGER = LogManager.getLogger(BasicMetaschemaTest.class);
66    @TempDir
67    Path generationDir;
68    // @NonNull
69    // Path generationDir =
70    // ObjectUtils.notNull(Paths.get("target/generated-test-sources/metaschema"));
71  
72    @NonNull
73    private static IModule loadModule(@NonNull Path moduleFile) throws MetaschemaException, IOException {
74      return LOADER.load(moduleFile);
75    }
76  
77    public static Class<?> compileModule(@NonNull Path moduleFile, @Nullable Path bindingFile,
78        @NonNull String rootClassName, @NonNull Path classDir)
79        throws IOException, ClassNotFoundException, MetaschemaException {
80      IModule module = loadModule(moduleFile);
81  
82      DefaultBindingConfiguration bindingConfiguration = new DefaultBindingConfiguration();
83      if (bindingFile != null && Files.exists(bindingFile) && Files.isRegularFile(bindingFile)) {
84        bindingConfiguration.load(bindingFile);
85      }
86  
87      ModuleCompilerHelper.compileModule(module, classDir, bindingConfiguration);
88  
89      // Load classes
90      return ModuleCompilerHelper.newClassLoader(
91          classDir,
92          ObjectUtils.notNull(Thread.currentThread().getContextClassLoader()))
93          .loadClass(rootClassName);
94    }
95  
96    private static Object read(@NonNull Format format, @NonNull Path file, @NonNull Class<?> rootClass)
97        throws IOException {
98      IBindingContext context = IBindingContext.instance();
99  
100     IDeserializer<?> deserializer = context.newDeserializer(format, rootClass);
101     LOGGER.info("Reading content: {}", file);
102     Object value = deserializer.deserialize(file);
103     return value;
104   }
105 
106   private static <CLASS> void write(@NonNull Format format, @NonNull Path file, CLASS rootObject) throws IOException {
107     IBindingContext context = IBindingContext.instance();
108     @SuppressWarnings("unchecked") Class<CLASS> clazz = (Class<CLASS>) rootObject.getClass();
109 
110     try (Writer writer = Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE,
111         StandardOpenOption.TRUNCATE_EXISTING)) {
112       assert writer != null;
113       context.newSerializer(format, clazz).serialize(rootObject, writer);
114     }
115   }
116 
117   private static void runTests(@NonNull String testPath, @NonNull String rootClassName, @NonNull Path classDir)
118       throws ClassNotFoundException, IOException, MetaschemaException, BindingException {
119     runTests(testPath, rootClassName, classDir, null);
120   }
121 
122   @SuppressWarnings("unused")
123   private static void runTests(
124       @NonNull String testPath,
125       @NonNull String rootClassName,
126       @NonNull Path classDir,
127       java.util.function.Consumer<Object> assertions)
128       throws ClassNotFoundException, IOException, MetaschemaException, BindingException {
129 
130     Class<?> rootClass = compileModule(
131         ObjectUtils.notNull(Paths.get(String.format("src/test/resources/metaschema/%s/metaschema.xml", testPath))),
132         Paths.get(String.format("src/test/resources/metaschema/%s/binding.xml", testPath)),
133         rootClassName,
134         classDir);
135 
136     assert rootClass != null;
137 
138     Path xmlExample = Paths.get(String.format("src/test/resources/metaschema/%s/example.xml",
139         testPath));
140     LOGGER.info("Testing XML file: {}", xmlExample.toString());
141     if (Files.exists(xmlExample)) {
142       String xml;
143       {
144         Object root = read(Format.XML, xmlExample, rootClass);
145         LOGGER.atDebug().log("Read XML: Object: {}", root.toString());
146         if (assertions != null) {
147           assertAll("Deserialize XML", () -> {
148             assertions.accept(root);
149           });
150         }
151 
152         LOGGER.atDebug().log("Write XML:");
153         write(Format.XML, ObjectUtils.notNull(Paths.get("target/out.xml")), root);
154 
155         LOGGER.atDebug().log("Write JSON:");
156         write(Format.XML, ObjectUtils.notNull(Paths.get("target/out.json")), root);
157       }
158 
159       Object root = read(Format.XML, ObjectUtils.notNull(Paths.get("target/out.xml")), rootClass);
160       if (assertions != null) {
161         assertAll("Deserialize XML (roundtrip)", () -> assertions.accept(root));
162       }
163     }
164   }
165 
166   @Test
167   void testSimpleMetaschema() throws MetaschemaException, IOException, ClassNotFoundException, BindingException {
168     runTests("simple", "gov.nist.csrc.ns.metaschema.testing.simple.TopLevel", ObjectUtils.notNull(generationDir));
169     // runTests("simple", "gov.nist.csrc.ns.metaschema.testing.simple.TopLevel",
170     // generationDir, (obj) ->
171     // {
172     // try {
173     // Assertions.assertEquals("test", reflectMethod(obj, "getId"));
174     // } catch (NoSuchMethodException | SecurityException e) {
175     // Assertions.fail(e);
176     // }
177     // });
178   }
179 
180   @Test
181   void testSimpleUuidMetaschema()
182       throws MetaschemaException, IOException, ClassNotFoundException, BindingException {
183     runTests(
184         "simple_with_uuid",
185         "gov.nist.csrc.ns.metaschema.testing.simple.with.uuid.TopLevel",
186         ObjectUtils.notNull(generationDir),
187         (obj) -> {
188           try {
189             Assertions.assertEquals("5de455cf-2f8d-4da2-9182-323d433e1065", reflectMethod(obj, "getUuid").toString());
190           } catch (NoSuchMethodException | SecurityException e) {
191             Assertions.fail(e);
192           }
193         });
194   }
195 
196   @Test
197   void testSimpleWithFieldMetaschema()
198       throws MetaschemaException, IOException, ClassNotFoundException, BindingException {
199     runTests(
200         "simple_with_field",
201         "gov.nist.csrc.ns.metaschema.testing.simple.with.field.TopLevel",
202         ObjectUtils.notNull(generationDir));
203   }
204 
205   private static Object reflectMethod(Object obj, String name) throws NoSuchMethodException {
206     return ReflectionUtils.invokeMethod(obj.getClass().getMethod(name), obj);
207   }
208 
209   @Test
210   void testFieldsWithFlagMetaschema()
211       throws MetaschemaException, IOException, ClassNotFoundException, BindingException {
212     runTests(
213         "fields_with_flags",
214         "gov.nist.csrc.ns.metaschema.testing.fields.with.flags.TopLevel",
215         ObjectUtils.notNull(generationDir),
216         (obj) -> {
217           try {
218             Assertions.assertEquals("test", reflectMethod(obj, "getId"));
219             Object field1 = ReflectionUtils.invokeMethod(obj.getClass().getMethod("getComplexField1"), obj);
220             Assertions.assertNotNull(field1);
221             Assertions.assertEquals("complex-field1", reflectMethod(field1, "getId"));
222             Assertions.assertEquals("test-string", reflectMethod(field1, "getValue"));
223 
224             @SuppressWarnings("unchecked") List<Object> field2s
225                 = (List<Object>) ReflectionUtils.invokeMethod(obj.getClass().getMethod("getComplexFields2"),
226                     obj);
227             Assertions.assertNotNull(field2s);
228             Assertions.assertEquals(1, field2s.size());
229             Object field2 = field2s.get(0);
230             Assertions.assertEquals("complex-field2-1", reflectMethod(field2, "getId"));
231             Assertions.assertEquals("test-string2", reflectMethod(field2, "getValue"));
232 
233             @SuppressWarnings("unchecked") List<Object> field3s
234                 = (List<Object>) ReflectionUtils.invokeMethod(obj.getClass().getMethod("getComplexFields3"),
235                     obj);
236             Assertions.assertEquals(2, field3s.size());
237             Assertions.assertAll("ComplexFields4 item", () -> {
238               Object item = field3s.get(0);
239               assertEquals("complex-field3-1", reflectMethod(item, "getId2"));
240               assertEquals("test-string3", reflectMethod(item, "getValue"));
241             });
242             Assertions.assertAll("ComplexFields4 item", () -> {
243               Object item = field3s.get(1);
244               assertEquals("complex-field3-2", reflectMethod(item, "getId2"));
245               assertEquals("test-string4", reflectMethod(item, "getValue"));
246             });
247 
248             Assertions.assertAll("ComplexFields4", () -> {
249               @SuppressWarnings("unchecked") Map<String, Object> collection
250                   = (Map<String, Object>) ReflectionUtils.invokeMethod(obj.getClass().getMethod("getComplexFields4"),
251                       obj);
252               Assertions.assertNotNull(collection);
253               Assertions.assertEquals(2, collection.size());
254               Set<Map.Entry<String, Object>> entries = collection.entrySet();
255               Iterator<Map.Entry<String, Object>> iter = entries.iterator();
256 
257               Assertions.assertAll("ComplexFields4 item", () -> {
258                 Map.Entry<String, Object> entry = iter.next();
259                 assertEquals("complex-field4-1", entry.getKey());
260                 assertEquals("complex-field4-1", reflectMethod(entry.getValue(), "getId2"));
261                 assertEquals("test-string5", reflectMethod(entry.getValue(), "getValue"));
262               });
263 
264               Assertions.assertAll("ComplexFields4 item", () -> {
265                 Map.Entry<String, Object> entry = iter.next();
266                 assertEquals("complex-field4-2", entry.getKey());
267                 assertEquals("complex-field4-2", reflectMethod(entry.getValue(), "getId2"));
268                 assertEquals("test-string6", reflectMethod(entry.getValue(), "getValue"));
269               });
270             });
271           } catch (NoSuchMethodException | SecurityException e) {
272             Assertions.fail(e);
273           }
274         });
275   }
276 
277   @Test
278   void testAssemblyMetaschema()
279       throws MetaschemaException, IOException, ClassNotFoundException, BindingException {
280     runTests(
281         "assembly",
282         "gov.nist.itl.metaschema.codegen.xml.example.assembly.TopLevel",
283         ObjectUtils.notNull(generationDir),
284         (obj) -> {
285           try {
286             Assertions.assertEquals("test", reflectMethod(obj, "getId"));
287           } catch (NoSuchMethodException | SecurityException e) {
288             Assertions.fail(e);
289           }
290         });
291   }
292 
293   @Test
294   void testLocalDefinitionsMetaschema()
295       throws MetaschemaException, IOException, ClassNotFoundException, BindingException {
296     runTests(
297         "local-definitions",
298         "gov.nist.csrc.ns.metaschema.testing.local.definitions.TopLevel",
299         ObjectUtils.notNull(generationDir));
300   }
301 }