001/*-
002 * #%L
003 * HAPI FHIR Storage api
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.dao.tx;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.interceptor.api.HookParams;
024import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
025import ca.uhn.fhir.interceptor.api.Pointcut;
026import ca.uhn.fhir.interceptor.model.RequestPartitionId;
027import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy;
028import ca.uhn.fhir.jpa.dao.DaoFailureUtil;
029import ca.uhn.fhir.jpa.model.config.PartitionSettings;
030import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
031import ca.uhn.fhir.rest.api.server.RequestDetails;
032import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
033import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
034import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
035import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
036import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
037import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
038import ca.uhn.fhir.util.ICallable;
039import ca.uhn.fhir.util.SleepUtil;
040import com.google.common.annotations.VisibleForTesting;
041import jakarta.annotation.Nonnull;
042import jakarta.annotation.Nullable;
043import org.apache.commons.lang3.Validate;
044import org.apache.commons.lang3.exception.ExceptionUtils;
045import org.hibernate.exception.ConstraintViolationException;
046import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049import org.springframework.beans.factory.annotation.Autowired;
050import org.springframework.dao.DataIntegrityViolationException;
051import org.springframework.dao.PessimisticLockingFailureException;
052import org.springframework.orm.ObjectOptimisticLockingFailureException;
053import org.springframework.transaction.PlatformTransactionManager;
054import org.springframework.transaction.TransactionStatus;
055import org.springframework.transaction.annotation.Isolation;
056import org.springframework.transaction.annotation.Propagation;
057import org.springframework.transaction.support.TransactionCallback;
058import org.springframework.transaction.support.TransactionCallbackWithoutResult;
059import org.springframework.transaction.support.TransactionOperations;
060import org.springframework.transaction.support.TransactionSynchronizationManager;
061import org.springframework.transaction.support.TransactionTemplate;
062
063import java.util.Objects;
064import java.util.concurrent.Callable;
065
066/**
067 * @see IHapiTransactionService for an explanation of this class
068 */
069public class HapiTransactionService implements IHapiTransactionService {
070
071        public static final String XACT_USERDATA_KEY_RESOLVED_TAG_DEFINITIONS =
072                        HapiTransactionService.class.getName() + "_RESOLVED_TAG_DEFINITIONS";
073        public static final String XACT_USERDATA_KEY_EXISTING_SEARCH_PARAMS =
074                        HapiTransactionService.class.getName() + "_EXISTING_SEARCH_PARAMS";
075        private static final Logger ourLog = LoggerFactory.getLogger(HapiTransactionService.class);
076        private static final ThreadLocal<RequestPartitionId> ourRequestPartitionThreadLocal = new ThreadLocal<>();
077        private static final ThreadLocal<HapiTransactionService> ourExistingTransaction = new ThreadLocal<>();
078
079        @Autowired
080        protected IInterceptorBroadcaster myInterceptorBroadcaster;
081
082        @Autowired
083        protected PlatformTransactionManager myTransactionManager;
084
085        @Autowired
086        protected IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
087
088        @Autowired
089        protected PartitionSettings myPartitionSettings;
090
091        private Propagation myTransactionPropagationWhenChangingPartitions = Propagation.REQUIRED;
092
093        private SleepUtil mySleepUtil = new SleepUtil();
094
095        @VisibleForTesting
096        public void setInterceptorBroadcaster(IInterceptorBroadcaster theInterceptorBroadcaster) {
097                myInterceptorBroadcaster = theInterceptorBroadcaster;
098        }
099
100        @VisibleForTesting
101        public void setSleepUtil(SleepUtil theSleepUtil) {
102                mySleepUtil = theSleepUtil;
103        }
104
105        @Override
106        public IExecutionBuilder withRequest(@Nullable RequestDetails theRequestDetails) {
107                return buildExecutionBuilder(theRequestDetails);
108        }
109
110        @Override
111        public IExecutionBuilder withSystemRequest() {
112                return buildExecutionBuilder(null);
113        }
114
115        protected IExecutionBuilder buildExecutionBuilder(@Nullable RequestDetails theRequestDetails) {
116                return new ExecutionBuilder(theRequestDetails);
117        }
118
119        /**
120         * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead
121         */
122        @Deprecated
123        public <T> T execute(
124                        @Nullable RequestDetails theRequestDetails,
125                        @Nullable TransactionDetails theTransactionDetails,
126                        @Nonnull TransactionCallback<T> theCallback) {
127                return execute(theRequestDetails, theTransactionDetails, theCallback, null);
128        }
129
130        /**
131         * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead
132         */
133        @Deprecated
134        public void execute(
135                        @Nullable RequestDetails theRequestDetails,
136                        @Nullable TransactionDetails theTransactionDetails,
137                        @Nonnull Propagation thePropagation,
138                        @Nonnull Isolation theIsolation,
139                        @Nonnull Runnable theCallback) {
140                TransactionCallbackWithoutResult callback = new TransactionCallbackWithoutResult() {
141                        @Override
142                        protected void doInTransactionWithoutResult(TransactionStatus status) {
143                                theCallback.run();
144                        }
145                };
146                execute(theRequestDetails, theTransactionDetails, callback, null, thePropagation, theIsolation);
147        }
148
149        /**
150         * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead
151         */
152        @Deprecated
153        @Override
154        public <T> T withRequest(
155                        @Nullable RequestDetails theRequestDetails,
156                        @Nullable TransactionDetails theTransactionDetails,
157                        @Nonnull Propagation thePropagation,
158                        @Nonnull Isolation theIsolation,
159                        @Nonnull ICallable<T> theCallback) {
160
161                TransactionCallback<T> callback = tx -> theCallback.call();
162                return execute(theRequestDetails, theTransactionDetails, callback, null, thePropagation, theIsolation);
163        }
164
165        /**
166         * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead
167         */
168        @Deprecated
169        public <T> T execute(
170                        @Nullable RequestDetails theRequestDetails,
171                        @Nullable TransactionDetails theTransactionDetails,
172                        @Nonnull TransactionCallback<T> theCallback,
173                        @Nullable Runnable theOnRollback) {
174                return execute(theRequestDetails, theTransactionDetails, theCallback, theOnRollback, null, null);
175        }
176
177        @SuppressWarnings("ConstantConditions")
178        /**
179         * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead
180         */
181        @Deprecated
182        public <T> T execute(
183                        @Nullable RequestDetails theRequestDetails,
184                        @Nullable TransactionDetails theTransactionDetails,
185                        @Nonnull TransactionCallback<T> theCallback,
186                        @Nullable Runnable theOnRollback,
187                        @Nullable Propagation thePropagation,
188                        @Nullable Isolation theIsolation) {
189                return withRequest(theRequestDetails)
190                                .withTransactionDetails(theTransactionDetails)
191                                .withPropagation(thePropagation)
192                                .withIsolation(theIsolation)
193                                .onRollback(theOnRollback)
194                                .execute(theCallback);
195        }
196
197        /**
198         * @deprecated Use {@link #withRequest(RequestDetails)} with fluent call instead
199         */
200        @Deprecated
201        public <T> T execute(
202                        @Nullable RequestDetails theRequestDetails,
203                        @Nullable TransactionDetails theTransactionDetails,
204                        @Nonnull TransactionCallback<T> theCallback,
205                        @Nullable Runnable theOnRollback,
206                        @Nonnull Propagation thePropagation,
207                        @Nonnull Isolation theIsolation,
208                        RequestPartitionId theRequestPartitionId) {
209                return withRequest(theRequestDetails)
210                                .withTransactionDetails(theTransactionDetails)
211                                .withPropagation(thePropagation)
212                                .withIsolation(theIsolation)
213                                .withRequestPartitionId(theRequestPartitionId)
214                                .onRollback(theOnRollback)
215                                .execute(theCallback);
216        }
217
218        public boolean isCustomIsolationSupported() {
219                return false;
220        }
221
222        @VisibleForTesting
223        public void setRequestPartitionSvcForUnitTest(IRequestPartitionHelperSvc theRequestPartitionHelperSvc) {
224                myRequestPartitionHelperSvc = theRequestPartitionHelperSvc;
225        }
226
227        public PlatformTransactionManager getTransactionManager() {
228                return myTransactionManager;
229        }
230
231        @VisibleForTesting
232        public void setTransactionManager(PlatformTransactionManager theTransactionManager) {
233                myTransactionManager = theTransactionManager;
234        }
235
236        @VisibleForTesting
237        public void setPartitionSettingsForUnitTest(PartitionSettings thePartitionSettings) {
238                myPartitionSettings = thePartitionSettings;
239        }
240
241        @Nullable
242        protected <T> T doExecute(ExecutionBuilder theExecutionBuilder, TransactionCallback<T> theCallback) {
243                final RequestPartitionId requestPartitionId = theExecutionBuilder.getEffectiveRequestPartitionId();
244                RequestPartitionId previousRequestPartitionId = null;
245                if (requestPartitionId != null) {
246                        previousRequestPartitionId = ourRequestPartitionThreadLocal.get();
247                        ourRequestPartitionThreadLocal.set(requestPartitionId);
248                }
249
250                ourLog.trace("Starting doExecute for RequestPartitionId {}", requestPartitionId);
251                if (!myPartitionSettings.isPartitioningEnabled()
252                                || Objects.equals(previousRequestPartitionId, requestPartitionId)) {
253                        if (ourExistingTransaction.get() == this && canReuseExistingTransaction(theExecutionBuilder)) {
254                                /*
255                                 * If we're already in an active transaction, and it's for the right partition,
256                                 * and it's not a read-only transaction, we don't need to open a new transaction
257                                 * so let's just add a method to the stack trace that makes this obvious.
258                                 */
259                                return executeInExistingTransaction(theCallback);
260                        }
261                }
262
263                HapiTransactionService previousExistingTransaction = ourExistingTransaction.get();
264                try {
265                        ourExistingTransaction.set(this);
266
267                        if (myTransactionPropagationWhenChangingPartitions == Propagation.REQUIRES_NEW) {
268                                return executeInNewTransactionForPartitionChange(
269                                                theExecutionBuilder, theCallback, requestPartitionId, previousRequestPartitionId);
270                        } else {
271                                return doExecuteInTransaction(
272                                                theExecutionBuilder, theCallback, requestPartitionId, previousRequestPartitionId);
273                        }
274                } finally {
275                        ourExistingTransaction.set(previousExistingTransaction);
276                }
277        }
278
279        @Nullable
280        private <T> T executeInNewTransactionForPartitionChange(
281                        ExecutionBuilder theExecutionBuilder,
282                        TransactionCallback<T> theCallback,
283                        RequestPartitionId requestPartitionId,
284                        RequestPartitionId previousRequestPartitionId) {
285                ourLog.trace("executeInNewTransactionForPartitionChange");
286                theExecutionBuilder.myPropagation = myTransactionPropagationWhenChangingPartitions;
287                return doExecuteInTransaction(theExecutionBuilder, theCallback, requestPartitionId, previousRequestPartitionId);
288        }
289
290        private boolean isThrowableOrItsSubclassPresent(Throwable theThrowable, Class<? extends Throwable> theClass) {
291                return ExceptionUtils.indexOfType(theThrowable, theClass) != -1;
292        }
293
294        private boolean isThrowablePresent(Throwable theThrowable, Class<? extends Throwable> theClass) {
295                return ExceptionUtils.indexOfThrowable(theThrowable, theClass) != -1;
296        }
297
298        private boolean isRetriable(Throwable theThrowable) {
299                return isThrowablePresent(theThrowable, ResourceVersionConflictException.class)
300                                || isThrowablePresent(theThrowable, DataIntegrityViolationException.class)
301                                || isThrowablePresent(theThrowable, ConstraintViolationException.class)
302                                || isThrowablePresent(theThrowable, ObjectOptimisticLockingFailureException.class)
303                                // calling isThrowableOrItsSubclassPresent instead of isThrowablePresent for
304                                // PessimisticLockingFailureException, because we want to retry on its subclasses as well,  especially
305                                // CannotAcquireLockException, which is thrown in some deadlock situations which we want to retry
306                                || isThrowableOrItsSubclassPresent(theThrowable, PessimisticLockingFailureException.class);
307        }
308
309        @Nullable
310        private <T> T doExecuteInTransaction(
311                        ExecutionBuilder theExecutionBuilder,
312                        TransactionCallback<T> theCallback,
313                        RequestPartitionId requestPartitionId,
314                        RequestPartitionId previousRequestPartitionId) {
315                ourLog.trace("doExecuteInTransaction");
316                try {
317                        for (int i = 0; ; i++) {
318                                try {
319
320                                        return doExecuteCallback(theExecutionBuilder, theCallback);
321
322                                } catch (Exception e) {
323                                        if (!isRetriable(e)) {
324                                                ourLog.debug("Unexpected transaction exception. Will not be retried.", e);
325                                                throw e;
326                                        } else {
327
328                                                ourLog.debug("Version conflict detected", e);
329
330                                                if (theExecutionBuilder.myOnRollback != null) {
331                                                        theExecutionBuilder.myOnRollback.run();
332                                                }
333
334                                                int maxRetries = 0;
335
336                                                /*
337                                                 * If two client threads both concurrently try to add the same tag that isn't
338                                                 * known to the system already, they'll both try to create a row in HFJ_TAG_DEF,
339                                                 * which is the tag definition table. In that case, a constraint error will be
340                                                 * thrown by one of the client threads, so we auto-retry in order to avoid
341                                                 * annoying spurious failures for the client.
342                                                 */
343                                                if (DaoFailureUtil.isTagStorageFailure(e)) {
344                                                        maxRetries = 3;
345                                                }
346
347                                                if (maxRetries == 0) {
348                                                        HookParams params = new HookParams()
349                                                                        .add(RequestDetails.class, theExecutionBuilder.myRequestDetails)
350                                                                        .addIfMatchesType(
351                                                                                        ServletRequestDetails.class, theExecutionBuilder.myRequestDetails);
352                                                        ResourceVersionConflictResolutionStrategy conflictResolutionStrategy =
353                                                                        (ResourceVersionConflictResolutionStrategy)
354                                                                                        CompositeInterceptorBroadcaster.doCallHooksAndReturnObject(
355                                                                                                        myInterceptorBroadcaster,
356                                                                                                        theExecutionBuilder.myRequestDetails,
357                                                                                                        Pointcut.STORAGE_VERSION_CONFLICT,
358                                                                                                        params);
359                                                        if (conflictResolutionStrategy != null && conflictResolutionStrategy.isRetry()) {
360                                                                maxRetries = conflictResolutionStrategy.getMaxRetries();
361                                                        }
362                                                }
363
364                                                if (i < maxRetries) {
365                                                        if (theExecutionBuilder.myTransactionDetails != null) {
366                                                                theExecutionBuilder
367                                                                                .myTransactionDetails
368                                                                                .getRollbackUndoActions()
369                                                                                .forEach(Runnable::run);
370                                                                theExecutionBuilder.myTransactionDetails.clearRollbackUndoActions();
371                                                                theExecutionBuilder.myTransactionDetails.clearResolvedItems();
372                                                                theExecutionBuilder.myTransactionDetails.clearUserData(
373                                                                                XACT_USERDATA_KEY_RESOLVED_TAG_DEFINITIONS);
374                                                                theExecutionBuilder.myTransactionDetails.clearUserData(
375                                                                                XACT_USERDATA_KEY_EXISTING_SEARCH_PARAMS);
376                                                        }
377                                                        double sleepAmount = (250.0d * i) * Math.random();
378                                                        long sleepAmountLong = (long) sleepAmount;
379                                                        mySleepUtil.sleepAtLeast(sleepAmountLong, false);
380
381                                                        ourLog.info(
382                                                                        "About to start a transaction retry due to conflict or constraint error. Sleeping {}ms first.",
383                                                                        sleepAmountLong);
384                                                        continue;
385                                                }
386
387                                                IBaseOperationOutcome oo = null;
388                                                if (e instanceof ResourceVersionConflictException) {
389                                                        oo = ((ResourceVersionConflictException) e).getOperationOutcome();
390                                                }
391
392                                                if (maxRetries > 0) {
393                                                        String msg =
394                                                                        "Max retries (" + maxRetries + ") exceeded for version conflict: " + e.getMessage();
395                                                        ourLog.info(msg, maxRetries);
396                                                        throw new ResourceVersionConflictException(Msg.code(549) + msg);
397                                                }
398
399                                                throw new ResourceVersionConflictException(Msg.code(550) + e.getMessage(), e, oo);
400                                        }
401                                }
402                        }
403                } finally {
404                        if (requestPartitionId != null) {
405                                ourRequestPartitionThreadLocal.set(previousRequestPartitionId);
406                        }
407                }
408        }
409
410        public void setTransactionPropagationWhenChangingPartitions(
411                        Propagation theTransactionPropagationWhenChangingPartitions) {
412                Validate.notNull(theTransactionPropagationWhenChangingPartitions);
413                myTransactionPropagationWhenChangingPartitions = theTransactionPropagationWhenChangingPartitions;
414        }
415
416        @Nullable
417        protected <T> T doExecuteCallback(ExecutionBuilder theExecutionBuilder, TransactionCallback<T> theCallback) {
418                try {
419                        TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
420
421                        if (theExecutionBuilder.myPropagation != null) {
422                                txTemplate.setPropagationBehavior(theExecutionBuilder.myPropagation.value());
423                        }
424
425                        if (isCustomIsolationSupported()
426                                        && theExecutionBuilder.myIsolation != null
427                                        && theExecutionBuilder.myIsolation != Isolation.DEFAULT) {
428                                txTemplate.setIsolationLevel(theExecutionBuilder.myIsolation.value());
429                        }
430
431                        if (theExecutionBuilder.myReadOnly) {
432                                txTemplate.setReadOnly(true);
433                        }
434
435                        return txTemplate.execute(theCallback);
436                } catch (MyException e) {
437                        if (e.getCause() instanceof RuntimeException) {
438                                throw (RuntimeException) e.getCause();
439                        } else {
440                                throw new InternalErrorException(Msg.code(551) + e);
441                        }
442                }
443        }
444
445        protected class ExecutionBuilder implements IExecutionBuilder, TransactionOperations, Cloneable {
446
447                private final RequestDetails myRequestDetails;
448                private Isolation myIsolation;
449                private Propagation myPropagation;
450                private boolean myReadOnly;
451                private TransactionDetails myTransactionDetails;
452                private Runnable myOnRollback;
453                protected RequestPartitionId myRequestPartitionId;
454
455                protected ExecutionBuilder(RequestDetails theRequestDetails) {
456                        myRequestDetails = theRequestDetails;
457                }
458
459                @Override
460                public ExecutionBuilder withIsolation(Isolation theIsolation) {
461                        assert myIsolation == null;
462                        myIsolation = theIsolation;
463                        return this;
464                }
465
466                @Override
467                public ExecutionBuilder withTransactionDetails(TransactionDetails theTransactionDetails) {
468                        assert myTransactionDetails == null;
469                        myTransactionDetails = theTransactionDetails;
470                        return this;
471                }
472
473                @Override
474                public ExecutionBuilder withPropagation(Propagation thePropagation) {
475                        assert myPropagation == null;
476                        myPropagation = thePropagation;
477                        return this;
478                }
479
480                @Override
481                public ExecutionBuilder withRequestPartitionId(RequestPartitionId theRequestPartitionId) {
482                        assert myRequestPartitionId == null;
483                        myRequestPartitionId = theRequestPartitionId;
484                        return this;
485                }
486
487                @Override
488                public ExecutionBuilder readOnly() {
489                        myReadOnly = true;
490                        return this;
491                }
492
493                @Override
494                public ExecutionBuilder onRollback(Runnable theOnRollback) {
495                        assert myOnRollback == null;
496                        myOnRollback = theOnRollback;
497                        return this;
498                }
499
500                @Override
501                public void execute(Runnable theTask) {
502                        TransactionCallback<Void> task = tx -> {
503                                theTask.run();
504                                return null;
505                        };
506                        execute(task);
507                }
508
509                @Override
510                public <T> T execute(Callable<T> theTask) {
511                        TransactionCallback<T> callback = tx -> invokeCallableAndHandleAnyException(theTask);
512                        return execute(callback);
513                }
514
515                @Override
516                public <T> T execute(@Nonnull TransactionCallback<T> callback) {
517                        assert callback != null;
518
519                        return doExecute(this, callback);
520                }
521
522                @VisibleForTesting
523                public RequestPartitionId getRequestPartitionIdForTesting() {
524                        return myRequestPartitionId;
525                }
526
527                @VisibleForTesting
528                public RequestDetails getRequestDetailsForTesting() {
529                        return myRequestDetails;
530                }
531
532                public Propagation getPropagation() {
533                        return myPropagation;
534                }
535
536                @Nullable
537                protected RequestPartitionId getEffectiveRequestPartitionId() {
538                        final RequestPartitionId requestPartitionId;
539                        if (myRequestPartitionId != null) {
540                                requestPartitionId = myRequestPartitionId;
541                        } else if (myRequestDetails != null) {
542                                requestPartitionId = myRequestPartitionHelperSvc.determineGenericPartitionForRequest(myRequestDetails);
543                        } else {
544                                requestPartitionId = null;
545                        }
546                        return requestPartitionId;
547                }
548        }
549
550        /**
551         * This is just an unchecked exception so that we can catch checked exceptions inside TransactionTemplate
552         * and rethrow them outside of it
553         */
554        static class MyException extends RuntimeException {
555
556                public MyException(Throwable theThrowable) {
557                        super(theThrowable);
558                }
559        }
560
561        /**
562         * Returns true if we alreadyt have an active transaction associated with the current thread, AND
563         * either it's non-read-only or we only need a read-only transaction, AND
564         * the newly requested transaction has a propagation of REQUIRED
565         */
566        private static boolean canReuseExistingTransaction(ExecutionBuilder theExecutionBuilder) {
567                return TransactionSynchronizationManager.isActualTransactionActive()
568                                && (!TransactionSynchronizationManager.isCurrentTransactionReadOnly() || theExecutionBuilder.myReadOnly)
569                                && (theExecutionBuilder.myPropagation == null
570                                                || theExecutionBuilder.myPropagation == Propagation.REQUIRED);
571        }
572
573        @Nullable
574        private static <T> T executeInExistingTransaction(@Nonnull TransactionCallback<T> theCallback) {
575                ourLog.trace("executeInExistingTransaction");
576                // TODO we could probably track the TransactionStatus we need as a thread local like we do our partition id.
577                return theCallback.doInTransaction(null);
578        }
579
580        /**
581         * Invokes {@link Callable#call()} and rethrows any exceptions thrown by that method.
582         * If the exception extends {@link BaseServerResponseException} it is rethrown unmodified.
583         * Otherwise, it's wrapped in a {@link InternalErrorException}.
584         */
585        public static <T> T invokeCallableAndHandleAnyException(Callable<T> theTask) {
586                try {
587                        return theTask.call();
588                } catch (BaseServerResponseException e) {
589                        throw e;
590                } catch (Exception e) {
591                        throw new InternalErrorException(Msg.code(2223) + e.getMessage(), e);
592                }
593        }
594
595        public static <T> T executeWithDefaultPartitionInContext(@Nonnull ICallable<T> theCallback) {
596                RequestPartitionId previousRequestPartitionId = ourRequestPartitionThreadLocal.get();
597                ourRequestPartitionThreadLocal.set(RequestPartitionId.defaultPartition());
598                try {
599                        return theCallback.call();
600                } finally {
601                        ourRequestPartitionThreadLocal.set(previousRequestPartitionId);
602                }
603        }
604
605        public static RequestPartitionId getRequestPartitionAssociatedWithThread() {
606                return ourRequestPartitionThreadLocal.get();
607        }
608
609        /**
610         * Throws an {@link IllegalArgumentException} if a transaction is active
611         */
612        public static void noTransactionAllowed() {
613                Validate.isTrue(
614                                !TransactionSynchronizationManager.isActualTransactionActive(),
615                                "Transaction must not be active but found an active transaction");
616        }
617
618        /**
619         * Throws an {@link IllegalArgumentException} if no transaction is active
620         */
621        public static void requireTransaction() {
622                Validate.isTrue(
623                                TransactionSynchronizationManager.isActualTransactionActive(),
624                                "Transaction required here but no active transaction found");
625        }
626}