88 *
99 * SPDX-License-Identifier: BSD-3-Clause
1010 *******************************************************************************/
11+ // Some portions generated by Codex
1112package org .eclipse .rdf4j .workbench .commands ;
1213
1314import java .io .IOException ;
1415import java .io .InputStream ;
1516import java .net .MalformedURLException ;
1617import java .net .URL ;
18+ import java .util .ArrayList ;
19+ import java .util .LinkedHashSet ;
1720import java .util .List ;
21+ import java .util .Locale ;
22+ import java .util .Set ;
1823
1924import javax .servlet .http .HttpServletResponse ;
2025
26+ import org .eclipse .rdf4j .common .transaction .IsolationLevel ;
27+ import org .eclipse .rdf4j .common .transaction .IsolationLevels ;
28+ import org .eclipse .rdf4j .common .transaction .TransactionSetting ;
29+ import org .eclipse .rdf4j .common .transaction .TransactionSettingRegistry ;
30+ import org .eclipse .rdf4j .http .protocol .Protocol ;
2131import org .eclipse .rdf4j .model .Resource ;
2232import org .eclipse .rdf4j .query .QueryResultHandlerException ;
2333import org .eclipse .rdf4j .repository .RepositoryConnection ;
3545public class AddServlet extends TransformationServlet {
3646
3747 private static final String URL = "url" ;
48+ private static final String ISOLATION_LEVEL_OPTION = "isolation-level-option" ;
49+ private static final String ISOLATION_LEVEL_OPTION_LABEL = "isolation-level-option-label" ;
50+ private static final String ISOLATION_LEVEL_PARAM = Protocol .TRANSACTION_SETTINGS_PREFIX + IsolationLevel .NAME ;
3851
3952 private final Logger logger = LoggerFactory .getLogger (AddServlet .class );
4053
@@ -44,37 +57,41 @@ protected void doPost(WorkbenchRequest req, HttpServletResponse resp, String xsl
4457 try {
4558 String baseURI = req .getParameter ("baseURI" );
4659 String contentType = req .getParameter ("Content-Type" );
60+ TransactionSetting isolationLevel = parseIsolationLevel (req );
4761 if (req .isParameterPresent (CONTEXT )) {
4862 Resource context = req .getResource (CONTEXT );
4963 if (req .isParameterPresent (URL )) {
50- add (req .getUrl (URL ), baseURI , contentType , context );
64+ add (req .getUrl (URL ), baseURI , contentType , isolationLevel , context );
5165 } else {
52- add (req .getContentParameter (), baseURI , contentType , req .getContentFileName (), context );
66+ add (req .getContentParameter (), baseURI , contentType , req .getContentFileName (), isolationLevel ,
67+ context );
5368 }
5469 } else {
5570 if (req .isParameterPresent (URL )) {
56- add (req .getUrl (URL ), baseURI , contentType );
71+ add (req .getUrl (URL ), baseURI , contentType , isolationLevel );
5772 } else {
58- add (req .getContentParameter (), baseURI , contentType , req .getContentFileName ());
73+ add (req .getContentParameter (), baseURI , contentType , req .getContentFileName (), isolationLevel );
5974 }
6075 }
6176 resp .sendRedirect ("summary" );
6277 } catch (BadRequestException exc ) {
6378 logger .warn (exc .toString (), exc );
6479 TupleResultBuilder builder = getTupleResultBuilder (req , resp , resp .getOutputStream ());
6580 builder .transform (xslPath , "add.xsl" );
66- builder .start ("error-message" , "baseURI" , CONTEXT , "Content-Type" );
81+ builder .start ("error-message" , "baseURI" , CONTEXT , "Content-Type" , ISOLATION_LEVEL_PARAM );
6782 builder .link (List .of (INFO ));
6883 String baseURI = req .getParameter ("baseURI" );
6984 String context = req .getParameter (CONTEXT );
7085 String contentType = req .getParameter ("Content-Type" );
71- builder .result (exc .getMessage (), baseURI , context , contentType );
86+ String isolationLevel = req .getParameter (ISOLATION_LEVEL_PARAM );
87+ builder .result (exc .getMessage (), baseURI , context , contentType , isolationLevel );
7288 builder .end ();
7389 }
7490 }
7591
7692 private void add (InputStream stream , String baseURI , String contentType , String contentFileName ,
77- Resource ... context ) throws BadRequestException , RepositoryException , IOException {
93+ TransactionSetting isolationLevel , Resource ... context )
94+ throws BadRequestException , RepositoryException , IOException {
7895 if (contentType == null ) {
7996 throw new BadRequestException ("No Content-Type provided" );
8097 }
@@ -90,13 +107,19 @@ private void add(InputStream stream, String baseURI, String contentType, String
90107 }
91108
92109 try (RepositoryConnection con = repository .getConnection ()) {
93- con .add (stream , baseURI , format , context );
94- } catch (RDFParseException | IllegalArgumentException exc ) {
95- throw new BadRequestException (exc .getMessage (), exc );
110+ boolean transactionStarted = beginIfRequested (con , isolationLevel );
111+ try {
112+ con .add (stream , baseURI , format , context );
113+ commitIfNeeded (con , transactionStarted );
114+ } catch (RDFParseException | IllegalArgumentException exc ) {
115+ rollbackIfNeeded (con , transactionStarted );
116+ throw new BadRequestException (exc .getMessage (), exc );
117+ }
96118 }
97119 }
98120
99- private void add (URL url , String baseURI , String contentType , Resource ... context )
121+ private void add (URL url , String baseURI , String contentType , TransactionSetting isolationLevel ,
122+ Resource ... context )
100123 throws BadRequestException , RepositoryException , IOException {
101124 if (contentType == null ) {
102125 throw new BadRequestException ("No Content-Type provided" );
@@ -114,7 +137,14 @@ private void add(URL url, String baseURI, String contentType, Resource... contex
114137
115138 try {
116139 try (RepositoryConnection con = repository .getConnection ()) {
117- con .add (url , baseURI , format , context );
140+ boolean transactionStarted = beginIfRequested (con , isolationLevel );
141+ try {
142+ con .add (url , baseURI , format , context );
143+ commitIfNeeded (con , transactionStarted );
144+ } catch (RDFParseException | MalformedURLException | IllegalArgumentException exc ) {
145+ rollbackIfNeeded (con , transactionStarted );
146+ throw exc ;
147+ }
118148 }
119149 } catch (RDFParseException | MalformedURLException | IllegalArgumentException exc ) {
120150 throw new BadRequestException (exc .getMessage (), exc );
@@ -124,11 +154,136 @@ private void add(URL url, String baseURI, String contentType, Resource... contex
124154 @ Override
125155 public void service (TupleResultBuilder builder , String xslPath )
126156 throws RepositoryException , QueryResultHandlerException {
127- // TupleResultBuilder builder = getTupleResultBuilder(req, resp);
128157 builder .transform (xslPath , "add.xsl" );
129158 builder .start ();
130159 builder .link (List .of (INFO ));
131160 builder .end ();
132161 }
133162
163+ @ Override
164+ protected void service (WorkbenchRequest req , HttpServletResponse resp , String xslPath ) throws Exception {
165+ TupleResultBuilder builder = getTupleResultBuilder (req , resp , resp .getOutputStream ());
166+ builder .transform (xslPath , "add.xsl" );
167+ builder .start (ISOLATION_LEVEL_OPTION , ISOLATION_LEVEL_OPTION_LABEL , ISOLATION_LEVEL_PARAM );
168+ builder .link (List .of (INFO ));
169+ String selected = req .getParameter (ISOLATION_LEVEL_PARAM );
170+ if (selected != null && !selected .isBlank ()) {
171+ builder .result (selected , isolationLevelLabel (selected ), selected );
172+ }
173+ for (String option : determineIsolationLevels ()) {
174+ if (!option .equals (selected )) {
175+ builder .result (option , isolationLevelLabel (option ), null );
176+ }
177+ }
178+ builder .end ();
179+ }
180+
181+ private TransactionSetting parseIsolationLevel (WorkbenchRequest req ) throws BadRequestException {
182+ String requested = req .getParameter (ISOLATION_LEVEL_PARAM );
183+ if (requested != null && !requested .isBlank ()) {
184+ return TransactionSettingRegistry .getInstance ()
185+ .get (IsolationLevel .NAME )
186+ .flatMap (factory -> factory .getTransactionSetting (requested ))
187+ .orElseThrow (() -> new BadRequestException ("Unknown isolation level: " + requested ));
188+ }
189+ return null ;
190+ }
191+
192+ private boolean beginIfRequested (RepositoryConnection connection , TransactionSetting isolationLevel )
193+ throws RepositoryException {
194+ if (isolationLevel != null ) {
195+ connection .begin (isolationLevel );
196+ return true ;
197+ }
198+ return false ;
199+ }
200+
201+ private void commitIfNeeded (RepositoryConnection connection , boolean transactionStarted )
202+ throws RepositoryException {
203+ if (transactionStarted && connection .isActive ()) {
204+ connection .commit ();
205+ }
206+ }
207+
208+ private void rollbackIfNeeded (RepositoryConnection connection , boolean transactionStarted ) {
209+ if (transactionStarted ) {
210+ try {
211+ if (connection .isActive ()) {
212+ connection .rollback ();
213+ }
214+ } catch (RepositoryException e ) {
215+ logger .warn ("Failed to roll back add transaction" , e );
216+ }
217+ }
218+ }
219+
220+ List <String > determineIsolationLevels () {
221+ if (repository == null ) {
222+ return List .of ();
223+ }
224+ Set <String > supported = new LinkedHashSet <>();
225+ try (RepositoryConnection connection = repository .getConnection ()) {
226+ IsolationLevel original = connection .getIsolationLevel ();
227+ for (IsolationLevels level : IsolationLevels .values ()) {
228+ if (supportsIsolationLevel (connection , level )) {
229+ supported .add (isolationLevelName (level ));
230+ }
231+ }
232+ if (original != null ) {
233+ String originalName = isolationLevelName (original );
234+ if (!supported .contains (originalName )) {
235+ supported .add (originalName );
236+ }
237+ }
238+ } catch (RepositoryException e ) {
239+ logger .warn ("Unable to determine supported isolation levels" , e );
240+ }
241+ return new ArrayList <>(supported );
242+ }
243+
244+ private boolean supportsIsolationLevel (RepositoryConnection connection , IsolationLevel level ) {
245+ try {
246+ connection .begin (level );
247+ connection .rollback ();
248+ return true ;
249+ } catch (RepositoryException e ) {
250+ try {
251+ if (connection .isActive ()) {
252+ connection .rollback ();
253+ }
254+ } catch (RepositoryException ex ) {
255+ logger .debug ("Unable to rollback after failed isolation test" , ex );
256+ }
257+ logger .debug ("Isolation level {} is not supported by {}" , level , repository .getClass ().getSimpleName (), e );
258+ return false ;
259+ }
260+ }
261+
262+ private String isolationLevelName (IsolationLevel level ) {
263+ String value = level .getValue ();
264+ if (value != null && !value .isBlank ()) {
265+ return value ;
266+ }
267+ return (level instanceof Enum <?>) ? ((Enum <?>) level ).name () : level .toString ();
268+ }
269+
270+ private String isolationLevelLabel (String value ) {
271+ String normalized = value .replace ('.' , '_' );
272+ String [] parts = normalized .toLowerCase (Locale .ROOT ).split ("_" );
273+ StringBuilder label = new StringBuilder ();
274+ for (String part : parts ) {
275+ if (part .isEmpty ()) {
276+ continue ;
277+ }
278+ if (label .length () > 0 ) {
279+ label .append (' ' );
280+ }
281+ label .append (Character .toUpperCase (part .charAt (0 )));
282+ if (part .length () > 1 ) {
283+ label .append (part .substring (1 ));
284+ }
285+ }
286+ return label .length () == 0 ? value : label .toString ();
287+ }
288+
134289}
0 commit comments