2222
2323import com .evolvedbinary .j8fu .function .BiConsumerE ;
2424import com .evolvedbinary .j8fu .function .ConsumerE ;
25- import com .evolvedbinary .j8fu .function .Function2E ;
2625import org .exist .security .PermissionDeniedException ;
2726import org .exist .source .Source ;
2827import org .exist .storage .BrokerPool ;
3433import java .io .IOException ;
3534import java .util .Properties ;
3635
36+ /**
37+ * Utility to make compiling and executing XQuery simpler
38+ * and safer by ensuring cleanup when the returned object is closed.
39+ *
40+ * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
41+ */
3742public class XQueryUtil {
3843
3944 /**
4045 * Execute an XPath or XQuery.
46+ * Performs both the compile and execute steps.
4147 *
4248 * @param broker the database broker.
4349 * @param source the source of the query.
@@ -60,6 +66,7 @@ public static XQueryUtil.QueryResult query(final DBBroker broker, final Source s
6066
6167 /**
6268 * Execute an XPath or XQuery.
69+ * Performs both the compile and execute steps.
6370 *
6471 * @param broker the database broker.
6572 * @param source the source of the query.
@@ -78,6 +85,26 @@ public static XQueryUtil.QueryResult query(final DBBroker broker, final Source s
7885 * @throws XPathException if the query raises an error.
7986 */
8087 public static XQueryUtil .QueryResult query (final DBBroker broker , final Source source , final boolean isXPointer , final boolean cacheQuery , @ Nullable final Sequence contextSequence , @ Nullable final Properties outputProperties , @ Nullable final ConsumerE <XQueryContext , XPathException > preCompilationContext , @ Nullable final ConsumerE <XQueryContext , XPathException > preExecutionContext , @ Nullable final BiConsumerE <XQueryContext , QueryResult , XPathException > postExecutionContext ) throws XPathException , PermissionDeniedException , IOException {
88+ final CompilationResult compilationResult = compile (broker , source , isXPointer , cacheQuery , preCompilationContext );
89+ return execute (broker , compilationResult , contextSequence , outputProperties , preExecutionContext , postExecutionContext );
90+ }
91+
92+ /**
93+ * Compile an XPath or XQuery.
94+ *
95+ * @param broker the database broker.
96+ * @param source the source of the query.
97+ * @param isXPointer true if the query is an XPointer, false otherwise.
98+ * @param cacheQuery true if the compiled query should be cached, false otherwise.
99+ * @param preCompilationContext access to the XQuery Context pre-compilation, or null if access is not required.
100+ *
101+ * @return the result of the compilation and the compilation time.
102+ *
103+ * @throws IOException if the source of the query cannot be read.
104+ * @throws PermissionDeniedException if the calling user does not have permission to execute the query.
105+ * @throws XPathException if the query raises an error.
106+ */
107+ public static CompilationResult compile (final DBBroker broker , final Source source , final boolean isXPointer , final boolean cacheQuery , @ Nullable final ConsumerE <XQueryContext , XPathException > preCompilationContext ) throws XPathException , PermissionDeniedException , IOException {
81108 final BrokerPool brokerPool = broker .getBrokerPool ();
82109 final XQuery xquery = brokerPool .getXQueryService ();
83110
@@ -104,7 +131,7 @@ public static XQueryUtil.QueryResult query(final DBBroker broker, final Source s
104131 compiledXquery .getContext ().updateContext (xqueryContext );
105132 xqueryContext .getWatchDog ().reset ();
106133
107- compilationTime = XQueryUtil .QueryResult .RETRIEVED_CACHED_COMPILED_QUERY ;
134+ compilationTime = XQueryUtil .CompilationResult .RETRIEVED_CACHED_COMPILED_QUERY ;
108135
109136 } else {
110137 xqueryContext = new XQueryContext (brokerPool );
@@ -118,146 +145,162 @@ public static XQueryUtil.QueryResult query(final DBBroker broker, final Source s
118145 compilationTime = System .currentTimeMillis () - compilationStart ;
119146 }
120147
148+ return new CompilationResult (source , xqueryPool , compiledXquery , xqueryContext , compilationTime );
149+
150+ } catch (final XPathException | PermissionDeniedException | IOException e ) {
151+ // Make sure to clean-up in case of an exception!
152+ if (xqueryContext != null ) {
153+ xqueryContext .runCleanupTasks ();
154+ }
155+
156+ if (xqueryPool != null && compiledXquery != null ) {
157+ xqueryPool .returnCompiledXQuery (source , compiledXquery );
158+ }
159+
160+ throw e ;
161+ }
162+ }
163+
164+ /**
165+ * Execute a compiled XPath or XQuery.
166+ *
167+ * @param broker the database broker.
168+ * @param compilationResult the compiled query.
169+ * @param contextSequence the context sequence to use when executing the query, or null if there is no context sequence.
170+ * @param outputProperties any output properties, or null.
171+ * @param preExecutionContext access to the XQuery Context pre-execution, or null if access is not required.
172+ * @param postExecutionContext access to the XQuery Context post-execution, or null if access is not required.
173+ *
174+ * @return the result of the query and compilation and execution times.
175+ *
176+ * @throws PermissionDeniedException if the calling user does not have permission to execute the query.
177+ * @throws XPathException if the query raises an error.
178+ */
179+ public static QueryResult execute (final DBBroker broker , final CompilationResult compilationResult , @ Nullable final Sequence contextSequence , @ Nullable final Properties outputProperties , @ Nullable final ConsumerE <XQueryContext , XPathException > preExecutionContext , @ Nullable final BiConsumerE <XQueryContext , QueryResult , XPathException > postExecutionContext ) throws XPathException , PermissionDeniedException {
180+ final BrokerPool brokerPool = broker .getBrokerPool ();
181+ final XQuery xquery = brokerPool .getXQueryService ();
182+
183+ try {
121184 if (preExecutionContext != null ) {
122- preExecutionContext .accept (xqueryContext );
185+ preExecutionContext .accept (compilationResult . xqueryContext );
123186 }
124187
125188 final long executionStart = System .currentTimeMillis ();
126- final Sequence result = xquery .execute (broker , compiledXquery , null , contextSequence , outputProperties , true );
189+ final Sequence result = xquery .execute (broker , compilationResult . compiledXquery , null , contextSequence , outputProperties , true );
127190 final long executionTime = System .currentTimeMillis () - executionStart ;
128191
129- final QueryResult queryResult = new XQueryUtil .QueryResult (source , xqueryPool , compiledXquery , xqueryContext , compilationTime , executionTime , result );
192+ final QueryResult queryResult = new XQueryUtil .QueryResult (compilationResult , executionTime , result );
130193
131194 if (postExecutionContext != null ) {
132- postExecutionContext .accept (xqueryContext , queryResult );
195+ postExecutionContext .accept (compilationResult . xqueryContext , queryResult );
133196 }
134197
135198 return queryResult ;
136199
137- } catch (final XPathException | PermissionDeniedException | IOException e ) {
200+ } catch (final XPathException | PermissionDeniedException e ) {
138201 // Make sure to clean-up in case of an exception!
139- if (xqueryContext != null ) {
140- xqueryContext .runCleanupTasks ();
141- }
202+ compilationResult .xqueryContext .runCleanupTasks ();
142203
143- if (xqueryPool != null && compiledXquery != null ) {
144- xqueryPool .returnCompiledXQuery (source , compiledXquery );
204+ if (compilationResult . xqueryPool != null ) {
205+ compilationResult . xqueryPool .returnCompiledXQuery (compilationResult . source , compilationResult . compiledXquery );
145206 }
146207
147208 throw e ;
148209 }
149210 }
150211
151- public static class QueryResult implements AutoCloseable {
212+ public static class CompilationResult implements AutoCloseable {
152213 /**
153214 * Indicates that the query did not need to be compiled as a cached version was available.
154215 */
155216 public static final int RETRIEVED_CACHED_COMPILED_QUERY = -1 ;
156217
157218 private final Source source ;
158219 private @ Nullable final XQueryPool xqueryPool ;
159- private @ Nullable final CompiledXQuery compiledXquery ;
220+ private final CompiledXQuery compiledXquery ;
160221 private final XQueryContext xqueryContext ;
222+ private boolean closed = false ;
161223
162224 public final long compilationTime ;
163- public final long executionTime ;
164- public final Sequence result ;
165225
166- public QueryResult (final Source source , @ Nullable final XQueryPool xqueryPool , @ Nullable final CompiledXQuery compiledXquery , final XQueryContext xqueryContext , final long compilationTime , final long executionTime , final Sequence result ) {
226+ public CompilationResult (final Source source , @ Nullable final XQueryPool xqueryPool , final CompiledXQuery compiledXquery , final XQueryContext xqueryContext , final long compilationTime ) {
167227 this .source = source ;
168228 this .xqueryPool = xqueryPool ;
169229 this .compiledXquery = compiledXquery ;
170230 this .xqueryContext = xqueryContext ;
171-
172231 this .compilationTime = compilationTime ;
173- this .executionTime = executionTime ;
174- this .result = result ;
175232 }
176233
177234 @ Override
178235 public void close () {
236+ if (closed ) {
237+ return ;
238+ }
239+
179240 // NOTE(AR) Only when the user has finished with the Query Result i.e. {@link #result}, can we then clean-up any associated resources.
180241 xqueryContext .runCleanupTasks ();
181242
182243 // Once we have cleaned-up if the query should be cached we return it to the query pool.
183244 if (xqueryPool != null && compiledXquery != null ) {
184245 xqueryPool .returnCompiledXQuery (source , compiledXquery );
185246 }
247+
248+ closed = true ;
186249 }
250+
187251 }
188252
189- /**
190- * Compile a query and perform an operation with the compiled query.
191- *
192- * @param <T> the type of the result of the operation.
193- *
194- * @param broker the database broker.
195- * @param source the source of the query.
196- *
197- * @param op the operation to perform with the compiled query.
198- *
199- * @return the result of the operation.
200- */
201- public static <T > T withCompiledQuery (final DBBroker broker , final Source source , final Function2E <CompiledXQuery , T , XPathException , PermissionDeniedException > op ) throws XPathException , PermissionDeniedException , IOException {
202- final BrokerPool pool = broker .getBrokerPool ();
203- final XQuery xqueryService = pool .getXQueryService ();
204- final XQueryPool xqueryPool = pool .getXQueryPool ();
205- final CompiledXQuery compiledQuery = compileQuery (broker , xqueryService , xqueryPool , source );
206- try {
207- return op .apply (compiledQuery );
208- } finally {
209- if (compiledQuery != null ) {
210- if (compiledQuery .getContext () != null ) {
211- compiledQuery .getContext ().runCleanupTasks ();
212- }
213- xqueryPool .returnCompiledXQuery (source , compiledQuery );
214- }
253+ public static class QueryResult implements AutoCloseable {
254+ private final Source source ;
255+ private @ Nullable final XQueryPool xqueryPool ;
256+ private @ Nullable final CompiledXQuery compiledXquery ;
257+ private final XQueryContext xqueryContext ;
258+ private boolean closed = false ;
259+
260+ public final long compilationTime ;
261+ public final long executionTime ;
262+ public final Sequence result ;
263+
264+ public QueryResult (final Source source , @ Nullable final XQueryPool xqueryPool , @ Nullable final CompiledXQuery compiledXquery , final XQueryContext xqueryContext , final long compilationTime , final long executionTime , final Sequence result ) {
265+ this .source = source ;
266+ this .xqueryPool = xqueryPool ;
267+ this .compiledXquery = compiledXquery ;
268+ this .xqueryContext = xqueryContext ;
269+
270+ this .compilationTime = compilationTime ;
271+ this .executionTime = executionTime ;
272+ this .result = result ;
215273 }
216- }
217274
218- private static CompiledXQuery compileQuery (final DBBroker broker , final XQuery xqueryService , final XQueryPool xqueryPool , final Source query ) throws PermissionDeniedException , XPathException , IOException {
219- @ Nullable CompiledXQuery compiled = null ;
220- @ Nullable XQueryContext context = null ;
221- try {
222- compiled = xqueryPool .borrowCompiledXQuery (broker , query );
223- if (compiled == null ) {
224- context = new XQueryContext (broker .getBrokerPool ());
225- } else {
226- context = compiled .getContext ();
227- context .prepareForReuse ();
228- }
275+ private QueryResult (final CompilationResult compilationResult , final long executionTime , final Sequence result ) {
276+ this .source = compilationResult .source ;
277+ this .xqueryPool = compilationResult .xqueryPool ;
278+ this .compiledXquery = compilationResult .compiledXquery ;
279+ this .xqueryContext = compilationResult .xqueryContext ;
229280
230- if (compiled == null ) {
231- compiled = xqueryService .compile (context , query );
232- } else {
233- compiled .getContext ().updateContext (context );
234- context .getWatchDog ().reset ();
235- }
281+ this .compilationTime = compilationResult .compilationTime ;
282+ this .executionTime = executionTime ;
283+ this .result = result ;
236284
237- return compiled ;
285+ // transfer ownership of the resources owned by compilationResult to this class
286+ compilationResult .closed = true ;
287+ }
238288
239- } catch (final PermissionDeniedException | XPathException | IOException e ) {
240- if (context != null ) {
241- context .runCleanupTasks ();
289+ @ Override
290+ public void close () {
291+ if (closed ) {
292+ return ;
242293 }
243- if (compiled != null ) {
244- xqueryPool .returnCompiledXQuery (query , compiled );
294+
295+ // NOTE(AR) Only when the user has finished with the Query Result i.e. {@link #result}, can we then clean-up any associated resources.
296+ xqueryContext .runCleanupTasks ();
297+
298+ // Once we have cleaned-up if the query should be cached we return it to the query pool.
299+ if (xqueryPool != null && compiledXquery != null ) {
300+ xqueryPool .returnCompiledXQuery (source , compiledXquery );
245301 }
246- throw e ;
247- }
248- }
249302
250- /**
251- * Execute a compiled query.
252- *
253- * @param broker the database broker.
254- * @param compiledXQuery the compiled query.
255- *
256- * @return the result sequence.
257- */
258- public static Sequence executeQuery (final DBBroker broker , final CompiledXQuery compiledXQuery ) throws PermissionDeniedException , XPathException {
259- final BrokerPool pool = broker .getBrokerPool ();
260- final XQuery xqueryService = pool .getXQueryService ();
261- return xqueryService .execute (broker , compiledXQuery , null , null , null , true );
303+ closed = true ;
304+ }
262305 }
263306}
0 commit comments