@@ -48,11 +48,114 @@ public void submitDatafilesQuery(String query)
4848 }
4949 }
5050
51+ /**
52+ * Utility class for calculating the count and size of a cart.
53+ */
54+ public class EntityCounter {
55+ private int getUrlLimit = Integer .parseInt (Properties .getInstance ().getProperty ("getUrlLimit" , "1024" ));
56+ public long totalSize = 0L ;
57+ public long totalCount = 0L ;
58+
59+ /**
60+ * Calculate the totalSize and totalCount of all ICAT Entities provided.
61+ *
62+ * @param investigationIds List of ICAT Investigation.id in the cart
63+ * @param datasetIds List of ICAT Dataset.id in the cart
64+ * @param datafileIds List of ICAT Datafile.id in the cart
65+ * @throws UnsupportedEncodingException if Entity ids cannot be URL encoded for ICAT queries
66+ * @throws TopcatException if ICAT query fails
67+ */
68+ public EntityCounter (List <Long > investigationIds , List <Long > datasetIds , List <Long > datafileIds )
69+ throws UnsupportedEncodingException , TopcatException {
70+
71+ processIds (investigationIds , "SELECT SUM(i.fileSize), SUM(i.fileCount) FROM Investigation i WHERE i.id IN (" );
72+ processIds (datasetIds , "SELECT SUM(d.fileSize), SUM(d.fileCount) FROM Dataset d WHERE d.id IN (" );
73+ processIds (datafileIds , "SELECT SUM(d.fileSize) FROM Datafile d WHERE d.id IN (" );
74+ totalCount += datafileIds .size ();
75+ }
76+
77+ /**
78+ * Process ids for either Investigations, Datasets or Datafile. These will be
79+ * chunked into a series of IN clauses to avoid exceeding the max url length.
80+ *
81+ * @param ids List of ICAT Entity.id
82+ * @param queryPrefix SELECT query up to but not including a chunked list of ids
83+ * @throws UnsupportedEncodingException if Entity ids cannot be URL encoded for ICAT queries
84+ * @throws TopcatException if ICAT query fails
85+ */
86+ private void processIds (List <Long > ids , String queryPrefix ) throws UnsupportedEncodingException , TopcatException {
87+ if (!ids .isEmpty ()) {
88+ int chunkLimit = getUrlLimit - minimumQuerySize - URLEncoder .encode (queryPrefix , "UTF8" ).length () - parenthesisSize ;
89+ ListIterator <Long > iterator = ids .listIterator ();
90+ Long id = iterator .next ();
91+ String chunkedIds = id .toString ();
92+ int chunkSize = URLEncoder .encode (chunkedIds , "UTF8" ).length ();
93+ while (iterator .hasNext ()) {
94+ id = iterator .next ();
95+ String idString = id .toString ();
96+ int encodedIdLength = URLEncoder .encode (idString , "UTF8" ).length ();
97+ if (chunkSize + commaSize + encodedIdLength > chunkLimit ) {
98+ submitIdsChunk (queryPrefix , chunkedIds );
99+ chunkedIds = idString ;
100+ chunkSize = encodedIdLength ;
101+ } else {
102+ chunkedIds += "," + idString ;
103+ chunkSize += commaSize + encodedIdLength ;
104+ }
105+ }
106+ submitIdsChunk (queryPrefix , chunkedIds );
107+ }
108+ }
109+
110+ /**
111+ * Submits and processes the result from a chunk of ids.
112+ *
113+ * @param queryPrefix SELECT query up to but not including a chunked list of ids
114+ * @param chunkedIds Comma separated list of ICAT Entity ids
115+ * @throws TopcatException if ICAT query fails
116+ */
117+ private void submitIdsChunk (String queryPrefix , String chunkedIds ) throws TopcatException {
118+ JsonArray jsonArray = submitQuery (queryPrefix + chunkedIds + ")" );
119+ JsonValue jsonValue = jsonArray .get (0 );
120+ if (jsonValue .getValueType ().equals (ValueType .NUMBER )) {
121+ totalSize += ((JsonNumber ) jsonValue ).longValueExact ();
122+ } else {
123+ JsonArray jsonArrayInner = jsonValue .asJsonArray ();
124+ if (jsonArrayInner .get (0 ).getValueType ().equals (ValueType .NUMBER )) {
125+ totalSize += jsonArrayInner .getJsonNumber (0 ).longValueExact ();
126+ }
127+ if (jsonArrayInner .size () > 1 && jsonArrayInner .get (1 ).getValueType ().equals (ValueType .NUMBER )) {
128+ totalCount += jsonArrayInner .getJsonNumber (1 ).longValueExact ();
129+ }
130+ }
131+ }
132+ }
133+
51134 private Logger logger = LoggerFactory .getLogger (IcatClient .class );
52135
53136 private HttpClient httpClient ;
54137 private String sessionId ;
55138
139+ private static final int minimumQuerySize = "entityManager?sessionId=&query=" .length () + 36 ; // sessionIds are 36 characters
140+ private static final int commaSize ;
141+ private static final int parenthesisSize ;
142+
143+ static {
144+ int parenthesisSizeNonFinal = 3 ;
145+ try {
146+ parenthesisSizeNonFinal = URLEncoder .encode (")" , "UTF8" ).length ();
147+ } catch (UnsupportedEncodingException e ) {} finally {
148+ parenthesisSize = parenthesisSizeNonFinal ;
149+ }
150+
151+ int commaSizeNonFinal = 3 ;
152+ try {
153+ commaSizeNonFinal = URLEncoder .encode ("," , "UTF8" ).length ();
154+ } catch (UnsupportedEncodingException e ) {} finally {
155+ commaSize = commaSizeNonFinal ;
156+ }
157+ }
158+
56159 public IcatClient (String url ) {
57160 this .httpClient = new HttpClient (url + "/icat" );
58161 }
@@ -201,12 +304,12 @@ public DatafilesResponse getDatafiles(List<String> files) throws TopcatException
201304 return response ;
202305 }
203306
204- // Total limit - "entityManager?sessionId=" - `sessionId` - "?query=" - `queryPrefix` - `querySuffix
205- // Limit is 1024 - 24 - 36 - 7 - 48 - 17
206- int getUrlLimit = Integer .parseInt (Properties .getInstance ().getProperty ("getUrlLimit" , "1024" ));
207- int chunkLimit = getUrlLimit - 132 ;
208307 String queryPrefix = "SELECT d from Datafile d WHERE d.location in (" ;
209308 String querySuffix = ") ORDER BY d.id" ;
309+ int getUrlLimit = Integer .parseInt (Properties .getInstance ().getProperty ("getUrlLimit" , "1024" ));
310+ int queryPrefixSize = URLEncoder .encode (queryPrefix , "UTF8" ).length ();
311+ int querySuffixSize = URLEncoder .encode (querySuffix , "UTF8" ).length ();
312+ int chunkLimit = getUrlLimit - minimumQuerySize - queryPrefixSize - querySuffixSize ;
210313 ListIterator <String > iterator = files .listIterator ();
211314
212315 String file = iterator .next ();
@@ -217,15 +320,15 @@ public DatafilesResponse getDatafiles(List<String> files) throws TopcatException
217320 file = iterator .next ();
218321 String quotedFile = "'" + file + "'" ;
219322 int encodedFileLength = URLEncoder .encode (quotedFile , "UTF8" ).length ();
220- if (chunkSize + 3 + encodedFileLength > chunkLimit ) {
323+ if (chunkSize + commaSize + encodedFileLength > chunkLimit ) {
221324 response .submitDatafilesQuery (queryPrefix + chunkedFiles + querySuffix );
222325
223326 chunkedFiles = quotedFile ;
224327 chunkSize = encodedFileLength ;
225328 response .missing .add (file );
226329 } else {
227330 chunkedFiles += "," + quotedFile ;
228- chunkSize += 3 + encodedFileLength ; // 3 is size of , when encoded as %2C
331+ chunkSize += commaSize + encodedFileLength ;
229332 response .missing .add (file );
230333 }
231334 }
@@ -291,7 +394,7 @@ public long getDatasetFileCount(long datasetId) throws TopcatException {
291394 * @throws TopcatException
292395 */
293396 public long getDatasetFileSize (long datasetId ) throws TopcatException {
294- String query = "SELECT SUM(datafile.fileSize) FROM Datafile datafile WHERE datafile.dataset.id = " + datasetId ;
397+ String query = "SELECT SUM(datafile.fileSize) FROM Datafile datafile WHERE datafile.dataset.id = " + datasetId ;
295398 JsonArray jsonArray = submitQuery (query );
296399 if (jsonArray .get (0 ).getValueType ().equals (ValueType .NUMBER )) {
297400 return jsonArray .getJsonNumber (0 ).longValueExact ();
0 commit comments