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