diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 29fbbd49ed9..7ab660f812d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -141,7 +141,7 @@ public abstract class BaseHapiFhirResourceDao extends B ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); } - + @Override public DaoMethodOutcome create(final T theResource) { return create(theResource, null, true, null); @@ -189,6 +189,11 @@ public abstract class BaseHapiFhirResourceDao extends B protected abstract IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode); + @Override + public DaoMethodOutcome delete(IIdType theId) { + return delete(theId, null); + } + @Override public ResourceTable delete(IIdType theId, List deleteConflicts, RequestDetails theRequestDetails) { if (theId == null || !theId.hasIdPart()) { @@ -225,11 +230,6 @@ public abstract class BaseHapiFhirResourceDao extends B return savedEntity; } - @Override - public DaoMethodOutcome delete(IIdType theId) { - return delete(theId, null); - } - @Override public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) { List deleteConflicts = new ArrayList(); @@ -242,7 +242,7 @@ public abstract class BaseHapiFhirResourceDao extends B ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); return toMethodOutcome(savedEntity, null); } - + @Override public List deleteByUrl(String theUrl, List deleteConflicts, RequestDetails theRequestDetails) { Set resource = processMatchUrl(theUrl, myResourceType); @@ -299,6 +299,13 @@ public abstract class BaseHapiFhirResourceDao extends B return new DaoMethodOutcome(); } + @PostConstruct + public void detectSearchDaoDisabled() { + if (mySearchDao != null && mySearchDao.isDisabled()) { + mySearchDao = null; + } + } + private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequestDetails) { StopWatch w = new StopWatch(); @@ -995,6 +1002,11 @@ public abstract class BaseHapiFhirResourceDao extends B return update(theResource, null, theRequestDetails); } + @Override + public DaoMethodOutcome update(T theResource, String theMatchUrl) { + return update(theResource, theMatchUrl, null); + } + @Override public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, RequestDetails theRequestDetails) { StopWatch w = new StopWatch(); @@ -1071,11 +1083,6 @@ public abstract class BaseHapiFhirResourceDao extends B return outcome; } - @Override - public DaoMethodOutcome update(T theResource, String theMatchUrl) { - return update(theResource, theMatchUrl, null); - } - @Override public DaoMethodOutcome update(T theResource, String theMatchUrl, RequestDetails theRequestDetails) { return update(theResource, theMatchUrl, true, theRequestDetails); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java index 2450170f735..499baed44a8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java @@ -27,6 +27,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import javax.persistence.EntityManager; @@ -381,4 +382,17 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem } + @Override + public boolean isDisabled() { + try { + FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); + em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get(); + } catch (Exception e) { + ourLog.trace("FullText test failed", e); + ourLog.debug("Hibernate Search (Lucene) appears to be disabled on this server, fulltext will be disabled"); + return true; + } + return false; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java index 15b6ebe0e60..3d97df40318 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java @@ -32,4 +32,6 @@ public interface IFulltextSearchSvc { List everything(String theResourceName, SearchParameterMap theParams); + boolean isDisabled(); + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3WithoutLuceneConfig.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3WithoutLuceneConfig.java new file mode 100644 index 00000000000..20bc3d6883c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3WithoutLuceneConfig.java @@ -0,0 +1,37 @@ +package ca.uhn.fhir.jpa.config; + +import java.util.Properties; + +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement() +public class TestDstu3WithoutLuceneConfig extends TestDstu3Config { + + @Override + @Bean() + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean(); + retVal.setPersistenceUnitName("PU_HapiFhirJpaDstu3"); + retVal.setDataSource(dataSource()); + retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity"); + retVal.setPersistenceProvider(new HibernatePersistenceProvider()); + retVal.setJpaProperties(jpaProperties()); + return retVal; + } + + private Properties jpaProperties() { + Properties extraProperties = new Properties(); + extraProperties.put("hibernate.format_sql", "false"); + extraProperties.put("hibernate.show_sql", "false"); + extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); + extraProperties.put("hibernate.search.autoregister_listeners", "false"); + return extraProperties; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchWithLuceneDisabledTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchWithLuceneDisabledTest.java new file mode 100644 index 00000000000..07ce3035286 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchWithLuceneDisabledTest.java @@ -0,0 +1,327 @@ +package ca.uhn.fhir.jpa.dao.dstu3; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsInRelativeOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.EntityManager; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; +import org.hl7.fhir.dstu3.model.AllergyIntolerance; +import org.hl7.fhir.dstu3.model.Appointment; +import org.hl7.fhir.dstu3.model.AuditEvent; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.CarePlan; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.CompartmentDefinition; +import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.Condition; +import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; +import org.hl7.fhir.dstu3.model.DateTimeType; +import org.hl7.fhir.dstu3.model.DateType; +import org.hl7.fhir.dstu3.model.Device; +import org.hl7.fhir.dstu3.model.DiagnosticOrder; +import org.hl7.fhir.dstu3.model.DiagnosticReport; +import org.hl7.fhir.dstu3.model.Encounter; +import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Immunization; +import org.hl7.fhir.dstu3.model.Location; +import org.hl7.fhir.dstu3.model.Media; +import org.hl7.fhir.dstu3.model.Medication; +import org.hl7.fhir.dstu3.model.MedicationOrder; +import org.hl7.fhir.dstu3.model.Meta; +import org.hl7.fhir.dstu3.model.NamingSystem; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.OperationDefinition; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Period; +import org.hl7.fhir.dstu3.model.Practitioner; +import org.hl7.fhir.dstu3.model.Quantity; +import org.hl7.fhir.dstu3.model.Questionnaire; +import org.hl7.fhir.dstu3.model.QuestionnaireResponse; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.Subscription; +import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.dstu3.model.Substance; +import org.hl7.fhir.dstu3.model.TemporalPrecisionEnum; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Transactional; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.config.TestDstu3Config; +import ca.uhn.fhir.jpa.config.TestDstu3WithoutLuceneConfig; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.dao.BaseJpaTest; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; +import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.entity.ResourceLink; +import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; +import ca.uhn.fhir.rest.api.SortOrderEnum; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.param.CompositeParam; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.HasParam; +import ca.uhn.fhir.rest.param.NumberParam; +import ca.uhn.fhir.rest.param.ParamPrefixEnum; +import ca.uhn.fhir.rest.param.QuantityParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.TokenParamModifier; +import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; + +// @RunWith(SpringJUnit4ClassRunner.class) +// @ContextConfiguration(classes= {TestDstu3WithoutLuceneConfig.class}) +// @SuppressWarnings("unchecked") +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { TestDstu3WithoutLuceneConfig.class }) +public class FhirResourceDaoDstu3SearchWithLuceneDisabledTest extends BaseJpaTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3SearchWithLuceneDisabledTest.class); + + @Autowired + @Qualifier("myAllergyIntoleranceDaoDstu3") + private IFhirResourceDao myAllergyIntoleranceDao; + @Autowired + @Qualifier("myAppointmentDaoDstu3") + private IFhirResourceDao myAppointmentDao; + @Autowired + @Qualifier("myAuditEventDaoDstu3") + private IFhirResourceDao myAuditEventDao; + @Autowired + @Qualifier("myBundleDaoDstu3") + private IFhirResourceDao myBundleDao; + @Autowired + @Qualifier("myCarePlanDaoDstu3") + private IFhirResourceDao myCarePlanDao; + @Autowired + @Qualifier("myCodeSystemDaoDstu3") + private IFhirResourceDao myCodeSystemDao; + @Autowired + @Qualifier("myCompartmentDefinitionDaoDstu3") + private IFhirResourceDao myCompartmentDefinitionDao; + @Autowired + @Qualifier("myConceptMapDaoDstu3") + private IFhirResourceDao myConceptMapDao; + @Autowired + @Qualifier("myConditionDaoDstu3") + private IFhirResourceDao myConditionDao; + @Autowired + protected DaoConfig myDaoConfig; + @Autowired + @Qualifier("myDeviceDaoDstu3") + private IFhirResourceDao myDeviceDao; + @Autowired + @Qualifier("myDiagnosticOrderDaoDstu3") + private IFhirResourceDao myDiagnosticOrderDao; + @Autowired + @Qualifier("myDiagnosticReportDaoDstu3") + private IFhirResourceDao myDiagnosticReportDao; + @Autowired + @Qualifier("myEncounterDaoDstu3") + private IFhirResourceDao myEncounterDao; + // @PersistenceContext() + @Autowired + private EntityManager myEntityManager; + @Autowired + @Qualifier("myFhirContextDstu3") + private FhirContext myFhirCtx; + @Autowired + @Qualifier("myImmunizationDaoDstu3") + private IFhirResourceDao myImmunizationDao; + @Autowired + @Qualifier("myLocationDaoDstu3") + private IFhirResourceDao myLocationDao; + @Autowired + @Qualifier("myMediaDaoDstu3") + private IFhirResourceDao myMediaDao; + @Autowired + @Qualifier("myMedicationDaoDstu3") + private IFhirResourceDao myMedicationDao; + @Autowired + @Qualifier("myMedicationOrderDaoDstu3") + private IFhirResourceDao myMedicationOrderDao; + @Autowired + @Qualifier("myNamingSystemDaoDstu3") + private IFhirResourceDao myNamingSystemDao; + @Autowired + @Qualifier("myObservationDaoDstu3") + private IFhirResourceDao myObservationDao; + @Autowired + @Qualifier("myOperationDefinitionDaoDstu3") + private IFhirResourceDao myOperationDefinitionDao; + @Autowired + @Qualifier("myOrganizationDaoDstu3") + private IFhirResourceDao myOrganizationDao; + @Autowired + @Qualifier("myPatientDaoDstu3") + private IFhirResourceDaoPatient myPatientDao; + @Autowired + @Qualifier("myPractitionerDaoDstu3") + private IFhirResourceDao myPractitionerDao; + @Autowired + @Qualifier("myQuestionnaireDaoDstu3") + private IFhirResourceDao myQuestionnaireDao; + @Autowired + @Qualifier("myQuestionnaireResponseDaoDstu3") + private IFhirResourceDao myQuestionnaireResponseDao; + @Autowired + @Qualifier("myResourceProvidersDstu3") + private Object myResourceProviders; + @Autowired + @Qualifier("myStructureDefinitionDaoDstu3") + private IFhirResourceDao myStructureDefinitionDao; + @Autowired + @Qualifier("mySubscriptionDaoDstu3") + private IFhirResourceDaoSubscription mySubscriptionDao; + @Autowired + @Qualifier("mySubstanceDaoDstu3") + private IFhirResourceDao mySubstanceDao; + @Autowired + @Qualifier("mySystemDaoDstu3") + private IFhirSystemDao mySystemDao; + @Autowired + @Qualifier("mySystemProviderDstu3") + private JpaSystemProviderDstu3 mySystemProvider; + + @Autowired + protected PlatformTransactionManager myTxManager; + + @Autowired + @Qualifier("myJpaValidationSupportChainDstu3") + private IValidationSupport myValidationSupport; + + @Before + @Transactional() + public void beforePurgeDatabase() { + final EntityManager entityManager = this.myEntityManager; + purgeDatabase(entityManager, myTxManager); + } + + @Before + public void beforeResetConfig() { + myDaoConfig.setHardSearchLimit(1000); + myDaoConfig.setHardTagListLimit(1000); + myDaoConfig.setIncludeLimit(2000); + } + + @Override + protected FhirContext getContext() { + return myFhirCtx; + } + + @Test + public void testSearchWithRegularParam() throws Exception { + String methodName = "testEverythingIncludesBackReferences"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Organization.SP_NAME, new StringParam(methodName)); + myOrganizationDao.search(map); + + } + + @Test + public void testSearchWithContent() throws Exception { + String methodName = "testEverythingIncludesBackReferences"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_CONTENT, new StringParam(methodName)); + try { + myOrganizationDao.search(map); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Fulltext search is not enabled on this service, can not process parameter: _content", e.getMessage()); + } + } + + @Test + public void testSearchWithText() throws Exception { + String methodName = "testEverythingIncludesBackReferences"; + + Organization org = new Organization(); + org.setName(methodName); + IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_TEXT, new StringParam(methodName)); + try { + myOrganizationDao.search(map); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Fulltext search is not enabled on this service, can not process parameter: _text", e.getMessage()); + } + } + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 2d9ff3e22e2..a73ad2df0bc 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -62,6 +62,12 @@ When using _elements]]> parameter on server, the server was not automatically adding the SUBSETTED]]> tag as it should + + JPA server should now automatically detect + if Hibernate Search (Lucene) is configured to be + disabled and will not attempt to use it. This + prevents a crash for some operations. +