Write CCSDS OEM XML to string

Hi forum.
I am implementing an exporter of OEM to String.

Let’s consider an arbitrary SpacecraftState sc

Here my code:

//Constants
static final String provider = "PROVIDER";
static final String planetTag = "EARTH";
static final int CCSDS_VERSION = 3;
 //Body
BodyFacade earthFacade = new BodyFacade(planetTag, CelestialBodyFactory.getCelestialBodies().getEarth());

//Set up header
Header header = new Header(CCSDS_VERSION);
header.setFormatVersion(CCSDS_VERSION);
header.setCreationDate(Time.getAbsoluteDateFromInstant(Instant.now()));
header.setOriginator(provider);

//Set up template
OemMetadata template = new OemMetadata(2);
template.setTimeSystem(TimeSystem.UTC);
template.setObjectName("sat");
template.setObjectID("sat");
template.setCenter(earthFacade);
template.setReferenceFrame(FrameFacade.map(FrameFactory.getEME2000()));
template.setStartTime(sc.getDate());
template.setStopTime(sc.getDate());

//Initialize segment
OemData oemData = new OemData();
oemData.addData(sc.getPVCoordinates(), false);
OemSegment segment = new OemSegment(template, oemData, sc.getMu());

//Write content
StringBuilder output = new StringBuilder();
XmlGenerator xmlGenerator = new XmlGenerator(output, XmlGenerator.DEFAULT_INDENT,
    "OEM Message", false);
WriterBuilder writer = new WriterBuilder();
writer.buildOemWriter().writeHeader(xmlGenerator, header);
writer.buildOemWriter().writeSegmentContent(xmlGenerator, CCSDS_VERSION, segment);

String oemString = output.toString();

Now the oemString I get is:

<?xml version="1.0" encoding="UTF-8"?>
<oem id="CCSDS_OEM_VERS" version="3.0">
  <header>
    <CREATION_DATE>2023-03-24T13:22:23.428</CREATION_DATE>
    <ORIGINATOR>PROVIDER</ORIGINATOR>
  </header>

  <body>

    <metadata>
      <OBJECT_NAME>sat</OBJECT_NAME>
      <OBJECT_ID>sat</OBJECT_ID>
      <CENTER_NAME>EARTH</CENTER_NAME>
      <REF_FRAME>EME2000</REF_FRAME>
      <TIME_SYSTEM>UTC</TIME_SYSTEM>
      <START_TIME>2024-01-02T10:00:00.0</START_TIME>
      <STOP_TIME>2024-01-02T10:00:00.0</STOP_TIME>
      <INTERPOLATION_DEGREE>2</INTERPOLATION_DEGREE>
    </metadata>

    <data>
      <stateVector>
        <EPOCH>2024-01-02T10:00:00.0</EPOCH>
        <X>4801.171325</X>
        <Y>2407.604966</Y>
        <Z>-4463.826906</Z>
        <X_DOT>4.842894043</X_DOT>
        <Y_DOT>0.9317044765000001</Y_DOT>
        <Z_DOT>5.716885176</Z_DOT>
      </stateVector>
    </data>

As you can notice, the writer does not complete the XML structure, so I miss the <\body> and <\oem > closures.

Am I doing something wrong?
Thanks in advance!
Alberto

You forgot to close the generator.

One way to do it automatically is to use a try-with-resources statement. Here is one example, directly copied from the unit tests:

            // write the parsed file back to a characters array
            final MessageWriter<H, S, F> writer = getWriter();
            final CharArrayWriter caw = new CharArrayWriter();
            try (Generator generator = format == FileFormat.KVN ?
                                       new KvnGenerator(caw, 25, "dummy.kvn", Constants.JULIAN_DAY, unitsColumn) :
                                       new XmlGenerator(caw, XmlGenerator.DEFAULT_INDENT, "dummy.xml",
                                                        Constants.JULIAN_DAY, unitsColumn > 0,
                                                        XmlGenerator.NDM_XML_V3_SCHEMA_LOCATION)) {
                writer.writeMessage(generator, original);
            }

            // reparse the written file
            final byte[]      bytes  = caw.toString().getBytes(StandardCharsets.UTF_8);
            final DataSource source2 = new DataSource(name, () -> new ByteArrayInputStream(bytes));
            final F          rebuilt = getParser().parseMessage(source2);

Here, we used CharArrayWriter instead of StringBuilder, but it should work the same. The point is that the CharArrayWriter/StringBuilder is outside of the try-with-resources, but the generator is limited to the scope of the try-with-resources. This implies its close method is called automatically (even in case of exception), so the toString method called later see the closing elements.

Another point: I noticed you use the version 3.0 of the ODM, which is not yet officially published by CCSDS. This means the file produced may not be parsable by other systems.
We expect the standard to be published soon though, as we have successfully completed the test phase between Orekit and STK. I’ll make the announcement on the forum as soon as I get the information from CCSDS navigation group. Even when the file format will become official, it may take some time before other implementations are able to handle it (but of course Orekit can already do that so if it is used on both sides it will work).

Thanks Luc.
Getting inspired by the test brought me to the solution.

      try (XmlGenerator xmlGenerator = new XmlGenerator(output, XmlGenerator.DEFAULT_INDENT,
          "OEM Message", false)) {
        WriterBuilder writer = new WriterBuilder();
        OemWriter oemWriter = writer.buildOemWriter();
        oemWriter.writeHeader(xmlGenerator, header);
        oemWriter.writeSegmentContent(xmlGenerator, CCSDS_VERSION, segment);
        oemWriter.writeFooter(xmlGenerator);
      }

I added the try-with-resources statement to close the </body> and writeFooter to close the </oem>.

I also moved to CCSDS version 2 for the moment, considering compatibility according to your suggestion.

Alberto