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