66import org .geppetto .core .datasources .GeppettoDataSourceException ;
77import org .geppetto .core .model .GeppettoModelAccess ;
88import org .geppetto .datasources .AQueryProcessor ;
9- import org .geppetto .model .datasources .AQueryResult ;
109import org .geppetto .model .datasources .DataSource ;
1110import org .geppetto .model .datasources .DatasourcesFactory ;
1211import org .geppetto .model .datasources .ProcessQuery ;
1615
1716/**
1817 * Converts the QueryResults emitted by VFBqueryResponseProcessor (one row per
19- * VFBquery `rows` entry, values still typed as Object) into the
20- * SerializableQueryResult shape the geppetto-vfb frontend expects (string-
21- * formatted cells, composite IDs for connectivity tables, synthesised
22- * "queried term" column where the v2 chain produced both Upstream_Class and
23- * Downstream_Class regardless of direction).
18+ * VFBquery `rows` entry, values still typed as Object and addressed by column
19+ * header name) into the SerializableQueryResult shape the geppetto-vfb
20+ * frontend expects (string- formatted cells, composite IDs for connectivity
21+ * tables, synthesised "queried term" column where the v2 chain produced both
22+ * Upstream_Class and Downstream_Class regardless of direction).
2423 *
2524 * Output shape is byte-equivalent to the existing SOLRQueryProcessor output
2625 * for hasClassConnectivity, so no frontend change is needed for the pilot.
2726 *
2827 * For non-connectivity VFBquery responses this processor degrades to a
29- * straight Object→String stringification per cell, which is the right
30- * behaviour for the Shape-A single-step migrations that come next.
28+ * straight Object-to-String pass-through of every column in header order,
29+ * which is the right behaviour for the Shape-A single-step migrations that
30+ * come next.
31+ *
32+ * Value access deliberately mirrors SOLRQueryProcessor.process(): iterate by
33+ * row index and pull each cell via results.getValue(headerName, rowIdx),
34+ * rather than calling .getValues() on the AQueryResult loop variable (which
35+ * the abstract parent does not expose).
3136 *
3237 * @author robertcourt
3338 */
@@ -38,6 +43,18 @@ public class VFBqueryJsonProcessor extends AQueryProcessor
3843
3944 private static final String DELIM = "----" ;
4045
46+ // Header titles emitted by VFBqueryResponseProcessor for class connectivity.
47+ // These are the `title` fields from the VFBquery /run_query response.
48+ private static final String COL_ID = "ID" ;
49+ private static final String COL_UPSTREAM = "Upstream Class" ;
50+ private static final String COL_DOWNSTREAM = "Downstream Class" ;
51+ private static final String COL_TOTAL_N = "Total N" ;
52+ private static final String COL_CONNECTED_N = "Connected N" ;
53+ private static final String COL_PERCENT = "% Connected" ;
54+ private static final String COL_PAIRWISE = "Pairwise Connections" ;
55+ private static final String COL_TOTAL_WEIGHT = "Total Weight" ;
56+ private static final String COL_AVG_WEIGHT = "Avg Weight" ;
57+
4158 private final Map <String , Object > processingOutputMap = new HashMap <String , Object >();
4259
4360 /*
@@ -63,19 +80,19 @@ public QueryResults process(ProcessQuery query, DataSource dataSource, Variable
6380
6481 QueryResults out = DatasourcesFactory .eINSTANCE .createQueryResults ();
6582
66- // Detect connectivity-style responses by header names . The upstream-class
67- // VFBquery call returns headers ["ID", "Upstream Class", "Total N", ...]
68- // (titles, not raw col ids); downstream returns ["ID", "Downstream Class", ...].
69- // We synthesise the missing column so output matches the v2 9-column shape.
70- boolean isUpstreamClassConnectivity = headerContains ( results , "Upstream Class" )
71- && !headerContains ( results , "Downstream Class" );
72- boolean isDownstreamClassConnectivity = headerContains ( results , "Downstream Class" )
73- && !headerContains ( results , "Upstream Class" );
74- boolean isClassConnectivity = isUpstreamClassConnectivity || isDownstreamClassConnectivity ;
83+ // Detect connectivity-style responses by header titles . The upstream-class
84+ // VFBquery call returns ["ID", "Upstream Class", "Total N", ...]; downstream
85+ // returns ["ID", "Downstream Class", ...]. We synthesise the missing column
86+ // so output matches the v2 9-column shape.
87+ boolean isUpstreamCall = results . getHeader (). contains ( COL_UPSTREAM )
88+ && !results . getHeader (). contains ( COL_DOWNSTREAM );
89+ boolean isDownstreamCall = results . getHeader (). contains ( COL_DOWNSTREAM )
90+ && !results . getHeader (). contains ( COL_UPSTREAM );
91+ boolean isClassConnectivity = isUpstreamCall || isDownstreamCall ;
7592
7693 if (isClassConnectivity )
7794 {
78- buildClassConnectivityRows (variable , results , out , isUpstreamClassConnectivity );
95+ buildClassConnectivityRows (variable , results , out , isUpstreamCall );
7996 }
8097 else
8198 {
@@ -86,7 +103,7 @@ public QueryResults process(ProcessQuery query, DataSource dataSource, Variable
86103 {
87104 System .out .println ("VFBqueryJsonProcessor: " + out .getResults ().size () + " rows in "
88105 + (System .currentTimeMillis () - t0 ) + " ms (mode="
89- + (isClassConnectivity ? (isUpstreamClassConnectivity ? "upstream-class" : "downstream-class" ) : "generic" )
106+ + (isClassConnectivity ? (isUpstreamCall ? "upstream-class" : "downstream-class" ) : "generic" )
90107 + ")" );
91108 }
92109
@@ -114,32 +131,22 @@ private void buildClassConnectivityRows(Variable variable, QueryResults in, Quer
114131 out .getHeader ().add ("Total_Weight" );
115132 out .getHeader ().add ("Avg_Weight" );
116133
117- // Resolve the column positions we need from the input header.
118- int idxId = headerIndex (in , "ID" );
119- int idxClass = upstreamCall ? headerIndex (in , "Upstream Class" ) : headerIndex (in , "Downstream Class" );
120- int idxTotalN = headerIndex (in , "Total N" );
121- int idxConnectedN = headerIndex (in , "Connected N" );
122- int idxPercent = headerIndex (in , "% Connected" );
123- int idxPairwise = headerIndex (in , "Pairwise Connections" );
124- int idxTotalWeight = headerIndex (in , "Total Weight" );
125- int idxAvgWeight = headerIndex (in , "Avg Weight" );
134+ String queriedId = variable != null && variable .getId () != null ? variable .getId () : "" ;
135+ // Markdown link form matches the rest of the VFB table column format —
136+ // the V3 frontend renders these as in-app navigation links. We don't
137+ // have the term's human label at this layer; the renderer resolves it
138+ // from the id, same as it does for the partner classes.
139+ String queriedMarkdown = "[" + queriedId + "](" + queriedId + ")" ;
126140
127- String queriedId = variable != null ? variable .getId () : "" ;
128- String queriedLabel = variable != null ? variable .getName () : "" ;
129- if (queriedLabel == null || queriedLabel .isEmpty ())
130- {
131- queriedLabel = queriedId ;
132- }
133- // Markdown link form matches the rest of the VFB table column format
134- // (the V3 frontend renders these as in-app navigation links).
135- String queriedMarkdown = "[" + queriedLabel + "](" + queriedId + ")" ;
141+ String partnerColumn = upstreamCall ? COL_UPSTREAM : COL_DOWNSTREAM ;
136142
137- for (AQueryResult row : in .getResults ())
143+ int n = in .getResults ().size ();
144+ for (int i = 0 ; i < n ; i ++)
138145 {
139146 SerializableQueryResult r = DatasourcesFactory .eINSTANCE .createSerializableQueryResult ();
140147
141- String partnerId = stringAt ( row , idxId );
142- String partnerMarkdown = stringAt ( row , idxClass );
148+ String partnerId = stringValue ( in , COL_ID , i );
149+ String partnerMarkdown = stringValue ( in , partnerColumn , i );
143150 String upstreamId = upstreamCall ? partnerId : queriedId ;
144151 String downstreamId = upstreamCall ? queriedId : partnerId ;
145152 String upstreamMarkdown = upstreamCall ? partnerMarkdown : queriedMarkdown ;
@@ -148,88 +155,85 @@ private void buildClassConnectivityRows(Variable variable, QueryResults in, Quer
148155 r .getValues ().add (upstreamId + DELIM + downstreamId );
149156 r .getValues ().add (upstreamMarkdown );
150157 r .getValues ().add (downstreamMarkdown );
151- r .getValues ().add (formatInt (valueAt ( row , idxTotalN ) , 6 ));
152- r .getValues ().add (formatInt (valueAt ( row , idxConnectedN ) , 6 ));
153- r .getValues ().add (formatPercent (valueAt ( row , idxPercent ) ));
154- r .getValues ().add (formatInt (valueAt ( row , idxPairwise ) , 8 ));
155- r .getValues ().add (formatInt (valueAt ( row , idxTotalWeight ) , 9 ));
156- r .getValues ().add (formatFloat (valueAt ( row , idxAvgWeight ) , 7 , 1 ));
158+ r .getValues ().add (formatInt (in , COL_TOTAL_N , i , 6 ));
159+ r .getValues ().add (formatInt (in , COL_CONNECTED_N , i , 6 ));
160+ r .getValues ().add (formatPercent (in , COL_PERCENT , i ));
161+ r .getValues ().add (formatInt (in , COL_PAIRWISE , i , 8 ));
162+ r .getValues ().add (formatInt (in , COL_TOTAL_WEIGHT , i , 9 ));
163+ r .getValues ().add (formatFloat (in , COL_AVG_WEIGHT , i , 7 , 1 ));
157164
158165 out .getResults ().add (r );
159166 }
160167 }
161168
162169 /**
163- * Generic Object→ String stringification for non-connectivity VFBquery
170+ * Generic Object-to- String stringification for non-connectivity VFBquery
164171 * responses. Used by Shape-A migrations (single-step Cypher queries) once
165172 * those land. Header titles are passed through unchanged.
166173 */
167174 private void buildGenericRows (QueryResults in , QueryResults out )
168175 {
169176 out .getHeader ().addAll (in .getHeader ());
170- for (AQueryResult row : in .getResults ())
177+ int n = in .getResults ().size ();
178+ for (int i = 0 ; i < n ; i ++)
171179 {
172180 SerializableQueryResult r = DatasourcesFactory .eINSTANCE .createSerializableQueryResult ();
173- for (Object v : row . getValues ())
181+ for (String col : in . getHeader ())
174182 {
183+ Object v = safeGetValue (in , col , i );
175184 r .getValues ().add (v == null ? "" : v .toString ());
176185 }
177186 out .getResults ().add (r );
178187 }
179188 }
180189
181- private static boolean headerContains (QueryResults r , String title )
182- {
183- return r .getHeader ().indexOf (title ) >= 0 ;
184- }
185-
186- private static int headerIndex (QueryResults r , String title )
187- {
188- return r .getHeader ().indexOf (title );
189- }
190-
191- private static Object valueAt (AQueryResult row , int idx )
190+ private static Object safeGetValue (QueryResults in , String col , int rowIdx )
192191 {
193- if (idx < 0 || idx >= row .getValues ().size ())
192+ try
193+ {
194+ return in .getValue (col , rowIdx );
195+ }
196+ catch (RuntimeException e )
194197 {
195198 return null ;
196199 }
197- return row .getValues ().get (idx );
198200 }
199201
200- private static String stringAt ( AQueryResult row , int idx )
202+ private static String stringValue ( QueryResults in , String col , int rowIdx )
201203 {
202- Object v = valueAt ( row , idx );
204+ Object v = safeGetValue ( in , col , rowIdx );
203205 return v == null ? "" : v .toString ();
204206 }
205207
206- private static String formatInt (Object v , int width )
208+ private static String formatInt (QueryResults in , String col , int rowIdx , int width )
207209 {
210+ Object v = safeGetValue (in , col , rowIdx );
208211 if (v == null )
209212 {
210213 return "" ;
211214 }
212- long n ;
215+ long ln ;
213216 if (v instanceof Number )
214217 {
215- n = ((Number ) v ).longValue ();
218+ ln = ((Number ) v ).longValue ();
216219 }
217220 else
218221 {
219222 try
220223 {
221- n = (long ) Double .parseDouble (v .toString ());
224+ ln = (long ) Double .parseDouble (v .toString ());
222225 }
223226 catch (NumberFormatException e )
224227 {
225228 return v .toString ();
226229 }
227230 }
228- return String .format ("%1$" + width + "d" , n );
231+ return String .format ("%1$" + width + "d" , ln );
229232 }
230233
231- private static String formatPercent (Object v )
234+ private static String formatPercent (QueryResults in , String col , int rowIdx )
232235 {
236+ Object v = safeGetValue (in , col , rowIdx );
233237 if (v == null )
234238 {
235239 return "" ;
@@ -253,8 +257,9 @@ private static String formatPercent(Object v)
253257 return String .format ("%5.1f%%" , d );
254258 }
255259
256- private static String formatFloat (Object v , int width , int precision )
260+ private static String formatFloat (QueryResults in , String col , int rowIdx , int width , int precision )
257261 {
262+ Object v = safeGetValue (in , col , rowIdx );
258263 if (v == null )
259264 {
260265 return "" ;
0 commit comments