View Javadoc
1   package ca.uhn.fhir.jpa.config;
2   
3   /*-
4    * #%L
5    * HAPI FHIR JPA Server
6    * %%
7    * Copyright (C) 2014 - 2019 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.i18n.HapiLocalizer;
24  import ca.uhn.fhir.jpa.model.entity.ForcedId;
25  import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
26  import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
27  import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
28  import org.hibernate.HibernateException;
29  import org.hibernate.StaleStateException;
30  import org.hibernate.exception.ConstraintViolationException;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  import org.springframework.dao.DataAccessException;
34  import org.springframework.dao.OptimisticLockingFailureException;
35  import org.springframework.orm.ObjectOptimisticLockingFailureException;
36  import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
37  
38  import javax.persistence.PersistenceException;
39  
40  import static org.apache.commons.lang3.StringUtils.defaultString;
41  import static org.apache.commons.lang3.StringUtils.isNotBlank;
42  
43  public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
44  
45  	private static final Logger ourLog = LoggerFactory.getLogger(HapiFhirHibernateJpaDialect.class);
46  	private HapiLocalizer myLocalizer;
47  
48  	/**
49  	 * Constructor
50  	 */
51  	public HapiFhirHibernateJpaDialect(HapiLocalizer theLocalizer) {
52  		myLocalizer = theLocalizer;
53  	}
54  
55  
56  	public RuntimeException translate(PersistenceException theException, String theMessageToPrepend) {
57  		if (theException.getCause() instanceof HibernateException) {
58  			return new PersistenceException(convertHibernateAccessException((HibernateException) theException.getCause(), theMessageToPrepend));
59  		}
60  		return theException;
61  	}
62  
63  	@Override
64  	protected DataAccessException convertHibernateAccessException(HibernateException theException) {
65  		return convertHibernateAccessException(theException, null);
66  	}
67  
68  	private DataAccessException convertHibernateAccessException(HibernateException theException, String theMessageToPrepend) {
69  		String messageToPrepend = "";
70  		if (isNotBlank(theMessageToPrepend)) {
71  			messageToPrepend = theMessageToPrepend + " - ";
72  		}
73  
74  		if (theException instanceof ConstraintViolationException) {
75  			String constraintName = ((ConstraintViolationException) theException).getConstraintName();
76  			switch (defaultString(constraintName)) {
77  				case ResourceHistoryTable.IDX_RESVER_ID_VER:
78  					throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure"));
79  				case ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING:
80  					throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceIndexedCompositeStringUniqueConstraintFailure"));
81  				case ForcedId.IDX_FORCEDID_TYPE_FID:
82  					throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "forcedIdConstraintFailure"));
83  			}
84  		}
85  
86  		/*
87  		 * It would be nice if we could be more precise here, since technically any optimistic lock
88  		 * failure could result in a StaleStateException, but with the error message we're returning
89  		 * we're basically assuming it's an optimistic lock failure on HFJ_RESOURCE.
90  		 *
91  		 * That said, I think this is an OK trade-off. There is a high probability that if this happens
92  		 * it is a failure on HFJ_RESOURCE (there aren't many other tables in our schema that
93  		 * use @Version at all) and this error message is infinitely more comprehensible
94  		 * than the one we'd otherwise return.
95  		 *
96  		 * The actual StaleStateException is thrown in hibernate's Expectations
97  		 * class in a method called "checkBatched" currently. This can all be tested using the
98  		 * StressTestR4Test method testMultiThreadedUpdateSameResourceInTransaction()
99  		 */
100 		if (theException instanceof StaleStateException) {
101 			String msg = messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure");
102 			throw new ResourceVersionConflictException(msg);
103 		}
104 
105 		return super.convertHibernateAccessException(theException);
106 	}
107 
108 }