11package gov .nasa .jpl .aerie .workspace .server ;
22
33import gov .nasa .jpl .aerie .workspace .server .postgres .RenderType ;
4+ import gov .nasa .jpl .aerie .workspace .server .types .MetadataKeys ;
45
56import javax .json .Json ;
67import javax .json .JsonArray ;
8+ import javax .json .JsonException ;
9+ import javax .json .JsonObject ;
710import javax .json .JsonObjectBuilder ;
11+ import javax .json .JsonValue ;
12+ import java .io .FileNotFoundException ;
13+ import java .io .FileReader ;
814import java .nio .file .Path ;
15+ import java .time .Instant ;
916import java .util .List ;
1017import java .util .Map ;
18+ import java .util .Optional ;
1119import java .util .TreeMap ;
1220
1321/**
@@ -29,7 +37,7 @@ public class DirectoryTree {
2937 * @param extensionMappings a map of file extensions to RenderTypes.
3038 * Used to determine the RenderType of file paths
3139 */
32- public DirectoryTree (Path root , List <Path > inputList , Map <String , RenderType > extensionMappings ) {
40+ public DirectoryTree (Path root , List <Path > inputList , Map <String , RenderType > extensionMappings , boolean withMetadata ) {
3341 if (!root .toFile ().isDirectory ()) {
3442 throw new IllegalArgumentException ("Cannot create a DirectoryTree from a file." );
3543 }
@@ -39,7 +47,8 @@ public DirectoryTree(Path root, List<Path> inputList, Map<String, RenderType> ex
3947 if (path .toFile ().isDirectory ()) {
4048 this .root .addChild (new DirectoryNode (path ));
4149 } else {
42- this .root .addChild (new FileNode (path , RenderType .getRenderType (path .getFileName ().toString (), extensionMappings )));
50+ final var rType = RenderType .getRenderType (path .getFileName ().toString (), extensionMappings );
51+ this .root .addChild (new FileNode (path , rType , withMetadata ));
4352 }
4453 }
4554 }
@@ -49,16 +58,125 @@ private static class FileNode {
4958 final String name ;
5059 final Path path ;
5160
61+ final Optional <JsonObject > metadata ;
62+ final Optional <MetadataStatus > metadataStatus ;
63+
64+ private enum MetadataStatus {
65+ ok , // Metadata file exists and is valid
66+ missing , // Metadata file is absent
67+ malformed // Metadata JSON is invalid
68+ }
69+
5270 FileNode (Path path , RenderType renderType ) {
5371 this .path = path ;
5472 this .renderType = renderType ;
5573 this .name = path .getFileName ().toString ();
74+ this .metadata = Optional .empty ();
75+ this .metadataStatus = Optional .empty ();
76+ }
77+
78+ FileNode (Path path , RenderType renderType , boolean getMetadata ) {
79+ this .path = path ;
80+ this .renderType = renderType ;
81+ this .name = path .getFileName ().toString ();
82+
83+ // Check if we even need to get the file's metadata
84+ if (!getMetadata || renderType == RenderType .METADATA ) {
85+ this .metadata = Optional .empty ();
86+ this .metadataStatus = Optional .empty ();
87+ return ;
88+ }
89+
90+ // Check if the metadata file exists
91+ final var metadataFile = this .path .resolveSibling (RenderType .toMetadataFileName (name )).toFile ();
92+ if (!metadataFile .exists () || metadataFile .isDirectory ()) {
93+ this .metadata = Optional .empty ();
94+ this .metadataStatus = Optional .of (MetadataStatus .missing );
95+ return ;
96+ }
97+
98+ // Attempt to read the metadata file
99+ final JsonObject fileContents ;
100+ try (final var reader = Json .createReader (new FileReader (metadataFile ))){
101+ fileContents = reader .readObject ();
102+ } catch (FileNotFoundException fnf ) {
103+ this .metadata = Optional .empty ();
104+ this .metadataStatus = Optional .of (MetadataStatus .missing );
105+ return ;
106+ } catch (JsonException je ) {
107+ this .metadata = Optional .empty ();
108+ this .metadataStatus = Optional .of (MetadataStatus .malformed );
109+ return ;
110+ }
111+
112+ // Validate metadata file
113+ if (!validateMetadataFile (fileContents )) {
114+ this .metadata = Optional .empty ();
115+ this .metadataStatus = Optional .of (MetadataStatus .malformed );
116+ return ;
117+ }
118+
119+ this .metadata = Optional .of (fileContents );
120+ this .metadataStatus = Optional .of (MetadataStatus .ok );
121+ }
122+
123+ private static boolean validateMetadataFile (JsonObject metadata ) throws JsonException {
124+ // Check that there's the right amount of keys
125+ if (metadata .size () > MetadataKeys .values ().length || metadata .size () < MetadataKeys .mandatoryKeys .size ()) {
126+ return false ;
127+ }
128+
129+ // Check that all the keys are real keys and the mandatory keys are present
130+ if (!MetadataKeys .keySet .containsAll (metadata .keySet ()) || !metadata .keySet ().containsAll (MetadataKeys .mandatoryKeys )) {
131+ return false ;
132+ }
133+
134+ // Validate that the keys have the correct types
135+ try {
136+ final var version = metadata .getString ("version" );
137+ // Version should be a recognized version
138+ if (!version .equals ("1" )) {
139+ return false ;
140+ }
141+
142+ metadata .getString ("createdBy" );
143+
144+ final var createdAt = metadata .getString ("createdAt" );
145+ Instant .parse (createdAt );
146+
147+ metadata .getString ("lastEditedBy" );
148+ final var lastEditedAt = metadata .getString ("lastEditedAt" );
149+ Instant .parse (lastEditedAt );
150+
151+
152+ // Validate that the optional keys have the correct type, if they're present
153+ if (metadata .containsKey ("readOnly" )) {
154+ metadata .getBoolean ("readOnly" );
155+ }
156+ if (metadata .containsKey ("user" )) {
157+ metadata .getJsonObject ("user" );
158+ }
159+ } catch (Exception cce ) {
160+ return false ;
161+ }
162+ return true ;
56163 }
57164
58165 JsonObjectBuilder toJsonBuilder () {
59- return Json .createObjectBuilder ()
60- .add ("name" , name )
61- .add ("type" , renderType .name ());
166+ final var builder = Json .createObjectBuilder ()
167+ .add ("name" , name )
168+ .add ("type" , renderType .name ());
169+
170+ metadataStatus .ifPresent (status -> {
171+ builder .add ("metadataStatus" , status .name ());
172+ if (status == MetadataStatus .ok && metadata .isPresent ()) {
173+ builder .add ("metadata" , metadata .get ());
174+ } else {
175+ builder .add ("metadata" , JsonValue .NULL );
176+ }
177+ });
178+
179+ return builder ;
62180 }
63181 }
64182
@@ -93,7 +211,12 @@ void addChild(FileNode child) {
93211 @ Override
94212 JsonObjectBuilder toJsonBuilder () {
95213 final var contentsArray = Json .createArrayBuilder ();
96- children .forEach ((key , child ) -> contentsArray .add (child .toJsonBuilder ()));
214+ children .forEach ((key , child ) -> {
215+ // Skip Metadata files by default
216+ if (child .renderType != RenderType .METADATA ) {
217+ contentsArray .add (child .toJsonBuilder ());
218+ }
219+ });
97220 return Json .createObjectBuilder ()
98221 .add ("name" , name )
99222 .add ("type" , renderType .name ())
@@ -102,11 +225,17 @@ JsonObjectBuilder toJsonBuilder() {
102225 }
103226
104227 /**
105- * Build a JsonArray representing the contents of this DirectoryTree
228+ * Build a JsonArray representing the contents of this DirectoryTree.
229+ * By default, skips METADATA type files.
106230 */
107231 public JsonArray toJson () {
108232 final var contentsArray = Json .createArrayBuilder ();
109- root .children .forEach ((key , child ) -> contentsArray .add (child .toJsonBuilder ()));
233+ root .children .forEach ((key , child ) -> {
234+ // Skip Metadata files
235+ if (child .renderType != RenderType .METADATA ) {
236+ contentsArray .add (child .toJsonBuilder ());
237+ }
238+ });
110239 return contentsArray .build ();
111240 }
112241}
0 commit comments