From 0430f6c8c33dd0191cbe0c2a6e09e64c3d44abc8 Mon Sep 17 00:00:00 2001 From: Volker Schmidt Date: Mon, 29 Oct 2018 17:09:37 +0100 Subject: [PATCH] Added separate cache support for FHIR Resources (None, Caffeine, Redis). --- .../main/resources/default-application.yml | 20 ++- app/src/main/resources/logback-spring.xml | 2 + common/pom.xml | 4 + .../cache/AbstractSimpleCacheConfig.java | 140 +++++++++++++++++ .../fhir/adapter/cache/SimpleCacheType.java | 39 +++++ .../cache/SimpleCaffeineCacheConfig.java | 61 ++++++++ .../adapter/cache/SimpleRedisCacheConfig.java | 84 ++++++++++ .../dhis/config/DhisMetadataCacheConfig.java | 61 ++++++++ .../impl/OrganisationUnitServiceImpl.java | 2 +- .../impl/ProgramMetadataServiceImpl.java | 2 +- .../TrackedEntityMetadataServiceImpl.java | 6 +- .../impl/FhirResourceCacheConfig.java | 58 +++++++ .../impl/FhirResourceRedisSerializer.java | 147 ++++++++++++++++++ .../impl/RemoteFhirRepositoryImpl.java | 3 +- 14 files changed, 617 insertions(+), 12 deletions(-) create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/cache/AbstractSimpleCacheConfig.java create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleCacheType.java create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleCaffeineCacheConfig.java create mode 100644 common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleRedisCacheConfig.java create mode 100644 dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisMetadataCacheConfig.java create mode 100644 fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceCacheConfig.java create mode 100644 fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceRedisSerializer.java diff --git a/app/src/main/resources/default-application.yml b/app/src/main/resources/default-application.yml index e0b76179..29251dba 100644 --- a/app/src/main/resources/default-application.yml +++ b/app/src/main/resources/default-application.yml @@ -39,10 +39,6 @@ spring: driver-class-name: @db.driver@ hikari: maximum-pool-size: 50 - cache: - type: caffeine - caffeine: - spec: expireAfterAccess=60s,maximumSize=10000 artemis: embedded: enabled: true @@ -50,7 +46,6 @@ spring: persistent: true data-directory: ${dhis2.home}/services/fhir-adapter/artemis - hystrix: command: default: @@ -73,6 +68,21 @@ dhis2.fhir-adapter: system-authentication: username: @dhis2.username@ password: @dhis2.password@ + cache: + dhis: + type: caffeine + caffeine: + spec: expireAfterAccess=60s,maximumSize=10000 + redis: + time-to-live: 60s + key-prefix: fhir-adapter:dhis + fhir: + type: caffeine + caffeine: + spec: expireAfterAccess=120s,maximumSize=10000 + redis: + time-to-live: 120s + key-prefix: fhir-adapter:fhir remote: processor: max-search-count: 10000 diff --git a/app/src/main/resources/logback-spring.xml b/app/src/main/resources/logback-spring.xml index 487a408d..6d17170a 100644 --- a/app/src/main/resources/logback-spring.xml +++ b/app/src/main/resources/logback-spring.xml @@ -41,6 +41,8 @@ + + diff --git a/common/pom.xml b/common/pom.xml index 6a8b4fa8..39414203 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -50,5 +50,9 @@ artemis-jms-server provided + + org.springframework.boot + spring-boot-starter-data-redis + diff --git a/common/src/main/java/org/dhis2/fhir/adapter/cache/AbstractSimpleCacheConfig.java b/common/src/main/java/org/dhis2/fhir/adapter/cache/AbstractSimpleCacheConfig.java new file mode 100644 index 00000000..54c75a11 --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/cache/AbstractSimpleCacheConfig.java @@ -0,0 +1,140 @@ +package org.dhis2.fhir.adapter.cache; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cache.CacheManager; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.cache.support.NoOpCacheManager; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.RedisElementReader; +import org.springframework.data.redis.serializer.RedisElementWriter; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Nonnull; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +/** + * Abstract simple cache configuration that can be extended multiple times for specific use cases. + */ +@Validated +public abstract class AbstractSimpleCacheConfig implements Serializable +{ + private static final long serialVersionUID = 3060542002074294407L; + + @NotNull + private SimpleCacheType type = SimpleCacheType.NONE; + + @Valid + @NotNull + private SimpleCaffeineCacheConfig caffeine = new SimpleCaffeineCacheConfig(); + + @Valid + @NotNull + private SimpleRedisCacheConfig redis = new SimpleRedisCacheConfig(); + + @Nonnull + public SimpleCacheType getType() + { + return type; + } + + public void setType( @Nonnull SimpleCacheType type ) + { + this.type = type; + } + + @Nonnull + public SimpleCaffeineCacheConfig getCaffeine() + { + return caffeine; + } + + public void setCaffeine( @Nonnull SimpleCaffeineCacheConfig caffeine ) + { + this.caffeine = caffeine; + } + + @Nonnull + public SimpleRedisCacheConfig getRedis() + { + return redis; + } + + public void setRedis( @Nonnull SimpleRedisCacheConfig redis ) + { + this.redis = redis; + } + + @Nonnull + protected CacheManager createCacheManager( @Nonnull ObjectProvider redisConnectionFactoryProvider, @Nonnull RedisSerializer redisSerializer ) + { + switch ( getType() ) + { + case NONE: + return new NoOpCacheManager(); + case CAFFEINE: + final CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager(); + caffeineCacheManager.setCacheSpecification( caffeine.getSpec() ); + return caffeineCacheManager; + case REDIS: + return RedisCacheManager.builder( redisConnectionFactoryProvider.getObject() ).cacheDefaults( createRedisCacheConfiguration( redisSerializer ) ).build(); + default: + throw new AssertionError( "Unhandled cache type: " + getType() ); + } + } + + @Nonnull + protected RedisCacheConfiguration createRedisCacheConfiguration( @Nonnull RedisSerializer redisSerializer ) + { + return RedisCacheConfiguration.defaultCacheConfig().computePrefixWith( getRedis().getKeyPrefix() ) + .entryTtl( getRedis().getTimeToLive() ).serializeValuesWith( new RedisSerializationContext.SerializationPair() + { + @Override + @Nonnull + public RedisElementReader getReader() + { + return RedisElementReader.from( redisSerializer ); + } + + @Override + @Nonnull + public RedisElementWriter getWriter() + { + return RedisElementWriter.from( redisSerializer ); + } + } ); + } +} diff --git a/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleCacheType.java b/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleCacheType.java new file mode 100644 index 00000000..fadadb06 --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleCacheType.java @@ -0,0 +1,39 @@ +package org.dhis2.fhir.adapter.cache; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * The type of simple cache to be used. + * + * @author volsch + */ +public enum SimpleCacheType +{ + NONE, CAFFEINE, REDIS +} diff --git a/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleCaffeineCacheConfig.java b/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleCaffeineCacheConfig.java new file mode 100644 index 00000000..81a3b885 --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleCaffeineCacheConfig.java @@ -0,0 +1,61 @@ +package org.dhis2.fhir.adapter.cache; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.apache.commons.lang3.StringUtils; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Nonnull; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +/** + * Simple cache configuration of a Caffeine cache. + * + * @author volsch + */ +@Validated +public class SimpleCaffeineCacheConfig implements Serializable +{ + private static final long serialVersionUID = 8632490027903813823L; + + @NotNull + private String spec = StringUtils.EMPTY; + + @Nonnull + public String getSpec() + { + return spec; + } + + public void setSpec( @Nonnull String spec ) + { + this.spec = spec; + } +} diff --git a/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleRedisCacheConfig.java b/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleRedisCacheConfig.java new file mode 100644 index 00000000..9f2a7aa6 --- /dev/null +++ b/common/src/main/java/org/dhis2/fhir/adapter/cache/SimpleRedisCacheConfig.java @@ -0,0 +1,84 @@ +package org.dhis2.fhir.adapter.cache; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.springframework.data.redis.cache.CacheKeyPrefix; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Nonnull; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.time.Duration; + +/** + * Simple cache configuration of a Redis cache. + * + * @author volsch + */ +@Validated +public class SimpleRedisCacheConfig implements Serializable +{ + private static final long serialVersionUID = 7668397012800515816L; + + @NotNull + private Duration timeToLive = Duration.ZERO; + + @NotNull + private CacheKeyPrefix keyPrefix = CacheKeyPrefix.simple(); + + @Nonnull + public Duration getTimeToLive() + { + return timeToLive; + } + + public void setTimeToLive( @Nonnull Duration timeToLive ) + { + this.timeToLive = timeToLive; + } + + @Nonnull + public CacheKeyPrefix getKeyPrefix() + { + return keyPrefix; + } + + public void setKeyPrefix( @Nonnull String keyPrefix ) + { + this.keyPrefix = new CacheKeyPrefix() + { + @Override + @Nonnull + public String compute( @Nonnull String cacheName ) + { + return keyPrefix + ":" + cacheName + ":"; + } + }; + } +} diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisMetadataCacheConfig.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisMetadataCacheConfig.java new file mode 100644 index 00000000..200180f4 --- /dev/null +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/config/DhisMetadataCacheConfig.java @@ -0,0 +1,61 @@ +package org.dhis2.fhir.adapter.dhis.config; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.dhis2.fhir.adapter.cache.AbstractSimpleCacheConfig; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Nonnull; + +/** + * Cache configuration for FHIR Resources. + */ +@Configuration +@ConfigurationProperties( "dhis2.fhir-adapter.cache.dhis" ) +@Validated +public class DhisMetadataCacheConfig extends AbstractSimpleCacheConfig +{ + private static final long serialVersionUID = 3060542002074294407L; + + @Primary + @Bean + @Nonnull + protected CacheManager dhisCacheManager( @Nonnull ObjectProvider redisConnectionFactoryProvider ) + { + return createCacheManager( redisConnectionFactoryProvider, new JdkSerializationRedisSerializer() ); + } +} diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/orgunit/impl/OrganisationUnitServiceImpl.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/orgunit/impl/OrganisationUnitServiceImpl.java index 62bb06a6..015fc7eb 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/orgunit/impl/OrganisationUnitServiceImpl.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/orgunit/impl/OrganisationUnitServiceImpl.java @@ -52,7 +52,7 @@ * @author volsch */ @Service -@CacheConfig( cacheNames = "organisationUnit" ) +@CacheConfig( cacheNames = "organisationUnit", cacheManager = "dhisCacheManager" ) public class OrganisationUnitServiceImpl implements OrganisationUnitService { protected static final String FIELDS = "id,code"; diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/tracker/program/impl/ProgramMetadataServiceImpl.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/tracker/program/impl/ProgramMetadataServiceImpl.java index 75259aac..591cf224 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/tracker/program/impl/ProgramMetadataServiceImpl.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/tracker/program/impl/ProgramMetadataServiceImpl.java @@ -54,7 +54,7 @@ * @author volsch */ @Service -@CacheConfig( cacheNames = "programMetadata" ) +@CacheConfig( cacheNames = "programMetadata", cacheManager = "dhisCacheManager" ) public class ProgramMetadataServiceImpl implements ProgramMetadataService { protected static final String FIELDS = diff --git a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/tracker/trackedentity/impl/TrackedEntityMetadataServiceImpl.java b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/tracker/trackedentity/impl/TrackedEntityMetadataServiceImpl.java index 8e850a8c..0f2da9fe 100644 --- a/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/tracker/trackedentity/impl/TrackedEntityMetadataServiceImpl.java +++ b/dhis/src/main/java/org/dhis2/fhir/adapter/dhis/tracker/trackedentity/impl/TrackedEntityMetadataServiceImpl.java @@ -73,7 +73,7 @@ public TrackedEntityMetadataServiceImpl( @Nonnull @Qualifier( "systemDhis2RestTe } @HystrixCommand - @Cacheable( "trackedEntityTypes" ) + @Cacheable( value = "trackedEntityTypes", cacheManager = "dhisCacheManager" ) @Nonnull @Override public Optional getType( @Nonnull Reference reference ) @@ -107,7 +107,7 @@ public Optional getType( @Nonnull Reference referen } @HystrixCommand - @Cacheable( "trackedEntityAttributes" ) + @Cacheable( value = "trackedEntityAttributes", cacheManager = "dhisCacheManager" ) @Nonnull @Override public TrackedEntityAttributes getAttributes() @@ -118,7 +118,7 @@ public TrackedEntityAttributes getAttributes() } @HystrixCommand - @Cacheable( "requiredValues" ) + @Cacheable( value = "requiredValues", cacheManager = "dhisCacheManager" ) @Override @Nonnull public RequiredValues getRequiredValues( @Nonnull String attributeId ) diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceCacheConfig.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceCacheConfig.java new file mode 100644 index 00000000..0cd24d91 --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceCacheConfig.java @@ -0,0 +1,58 @@ +package org.dhis2.fhir.adapter.fhir.repository.impl; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.dhis2.fhir.adapter.cache.AbstractSimpleCacheConfig; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Nonnull; + +/** + * Cache configuration for FHIR Resources. + */ +@Configuration +@ConfigurationProperties( "dhis2.fhir-adapter.cache.fhir" ) +@Validated +public class FhirResourceCacheConfig extends AbstractSimpleCacheConfig +{ + private static final long serialVersionUID = 3060542002074294407L; + + @Bean + @Nonnull + protected CacheManager fhirCacheManager( @Nonnull ObjectProvider redisConnectionFactoryProvider, @Nonnull FhirResourceRedisSerializer redisSerializer ) + { + return createCacheManager( redisConnectionFactoryProvider, redisSerializer ); + } +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceRedisSerializer.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceRedisSerializer.java new file mode 100644 index 00000000..2448f671 --- /dev/null +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/FhirResourceRedisSerializer.java @@ -0,0 +1,147 @@ +package org.dhis2.fhir.adapter.fhir.repository.impl; + +/* + * Copyright (c) 2004-2018, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import ca.uhn.fhir.context.FhirContext; +import org.dhis2.fhir.adapter.fhir.model.FhirVersion; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Serializer for HAPI FHIR Resources for caching them in Redis. + * + * @author volsch + */ +@Component +public class FhirResourceRedisSerializer implements RedisSerializer +{ + private static final byte[] EMPTY_ARRAY = new byte[0]; + + private final Map fhirContexts; + + public FhirResourceRedisSerializer( @Nonnull ObjectProvider> fhirContexts ) + { + this.fhirContexts = fhirContexts.getIfAvailable( Collections::emptyList ).stream().filter( fc -> (FhirVersion.get( fc.getVersion().getVersion() ) != null) ) + .collect( Collectors.toMap( fc -> FhirVersion.get( fc.getVersion().getVersion() ), fc -> fc ) ); + } + + @Override + public byte[] serialize( @Nullable IBaseResource resource ) throws SerializationException + { + if ( resource == null ) + { + return EMPTY_ARRAY; + } + + final FhirVersion fhirVersion = FhirVersion.get( resource.getStructureFhirVersionEnum() ); + if ( fhirVersion == null ) + { + throw new SerializationException( "Could not serialize FHIR resource since FHIR version " + resource.getStructureFhirVersionEnum() + " is not supported." ); + } + final FhirContext context = fhirContexts.get( fhirVersion ); + if ( context == null ) + { + throw new SerializationException( "Could not serialize FHIR resource since FHIR context is not available for FHIR version " + fhirVersion + "." ); + } + + final ByteArrayOutputStream bs = new ByteArrayOutputStream( 512 ); + try + { + final DataOutputStream out = new DataOutputStream( bs ); + out.writeUTF( fhirVersion.name() ); + final Writer w = new OutputStreamWriter( out, StandardCharsets.UTF_8 ); + context.newJsonParser().encodeResourceToWriter( resource, w ); + // flush internal buffers to byte array output stream + w.close(); + out.close(); + bs.close(); + } + catch ( IOException e ) + { + throw new SerializationException( "Could not serialize FHIR resource.", e ); + } + + return bs.toByteArray(); + } + + @Override + public IBaseResource deserialize( byte[] bytes ) throws SerializationException + { + if ( (bytes == null) || (bytes.length == 0) ) + { + return null; + } + + final DataInputStream in = new DataInputStream( new ByteArrayInputStream( bytes ) ); + try + { + final String fhirVersionString = in.readUTF(); + final FhirVersion fhirVersion; + try + { + fhirVersion = FhirVersion.valueOf( fhirVersionString ); + } + catch ( IllegalArgumentException e ) + { + throw new SerializationException( "Unknown FHIR version: " + fhirVersionString ); + } + + final FhirContext context = fhirContexts.get( fhirVersion ); + if ( context == null ) + { + throw new SerializationException( "Could not deserialize FHIR resource since FHIR context is not available for FHIR version " + fhirVersion + "." ); + } + + return context.newJsonParser().parseResource( new InputStreamReader( in, StandardCharsets.UTF_8 ) ); + } + catch ( IOException e ) + { + throw new SerializationException( "Could not deserialize FHIR resource.", e ); + } + } +} diff --git a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/RemoteFhirRepositoryImpl.java b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/RemoteFhirRepositoryImpl.java index dc72e8f0..435056dc 100644 --- a/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/RemoteFhirRepositoryImpl.java +++ b/fhir/src/main/java/org/dhis2/fhir/adapter/fhir/repository/impl/RemoteFhirRepositoryImpl.java @@ -62,7 +62,7 @@ * @author volsch */ @Component -@CacheConfig( cacheNames = "fhirResources" ) +@CacheConfig( cacheNames = "fhirResources", cacheManager = "fhirCacheManager" ) public class RemoteFhirRepositoryImpl implements RemoteFhirRepository { private final Logger logger = LoggerFactory.getLogger( getClass() ); @@ -74,7 +74,6 @@ public class RemoteFhirRepositoryImpl implements RemoteFhirRepository public RemoteFhirRepositoryImpl( @Nonnull RemoteSubscriptionRepository repository, @Nonnull ObjectProvider> fhirContexts ) { this.repository = repository; - this.fhirContexts = fhirContexts.getIfAvailable( Collections::emptyList ).stream().filter( fc -> (FhirVersion.get( fc.getVersion().getVersion() ) != null) ) .collect( Collectors.toMap( fc -> FhirVersion.get( fc.getVersion().getVersion() ), fc -> fc ) ); }