diff --git a/.java-version b/.java-version index 98d9bcb75..5f39e9144 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -17 +21.0 diff --git a/felles/abac/pom.xml b/felles/abac/pom.xml index baca8823e..98c5a8a46 100644 --- a/felles/abac/pom.xml +++ b/felles/abac/pom.xml @@ -19,8 +19,8 @@ felles-feil - no.nav.foreldrepenger - konfig + no.nav.foreldrepenger.felles + felles-konfig no.nav.foreldrepenger.felles diff --git a/felles/konfig/README.md b/felles/konfig/README.md new file mode 100644 index 000000000..3fddd6888 --- /dev/null +++ b/felles/konfig/README.md @@ -0,0 +1,91 @@ +### OBS!!! Deprecated - funksjonalitet ble flyttet til fp-felles og utvikler videre der. + +[![Bygg](https://github.com/navikt/fp-konfig/actions/workflows/build.yml/badge.svg)](https://github.com/navikt/fp-konfig/actions/workflows/build.yml) + +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=navikt_fp-konfig&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=navikt_fp-konfig) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=navikt_fp-konfig&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=navikt_fp-konfig) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=navikt_fp-konfig&metric=coverage)](https://sonarcloud.io/summary/new_code?id=navikt_fp-konfig) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=navikt_fp-konfig&metric=bugs)](https://sonarcloud.io/summary/new_code?id=navikt_fp-konfig) +[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=navikt_fp-konfig&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=navikt_fp-konfig) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=navikt_fp-konfig&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=navikt_fp-konfig) + +![GitHub release (latest by date)](https://img.shields.io/github/v/release/navikt/fp-konfig) +![GitHub](https://img.shields.io/github/license/navikt/fp-konfig) + + +# fp-konfig + +Java/CDI basert bibliotek som brukes til å håndtere app konfigurasjon avhengig av kubernetes cluster og namespace applikasjonen kjører i. + +Det er mulig å injecte en spesifikk variable i en konstruktor eller felt ved å bruke ``@KonfigVerdi`` annotering. + +## Bruk + +### Konfig kilder + +Biblioteket leter etter konfig i følgende kilder og bruker første verdi fra kilden hvor den finnes. + +Prioriteten på kilder er som følge: + +- Applikasjons properties med cluster og namespace (*application-prod-fss-default.properties*) +- Applikasjons properties med cluster (*application-prod-fss.properties*) +- Applikasjons properties (*application.properties*) +- Miljø variabler (generelt alt som finnes i *System.getenv()*) +- System properties (generelt alt som finnes i *System.getProperties()*) + +For kjøring utenfor kubernetes f.eks. fra IDE defaultes cluster navn til "local" og namespace til verdi satt +i System.getProperty("app.name"); +Dvs at man kan nå properties under application.properties, application-lokal.properties og application-lokal-.properties + +Det er mulig å plugge inn en egen provider ved å implementere `PropertiesKonfigVerdiProvider` klassen. + +Løsningen er CDI basert. + +### Bruk av `@KonfigVerdi` + +Det er mulig å direkte `@Injecte` konfig som trenger ved å bruke `@KonfigVerdi` annotering i konstruktor eller direkte på et attribut. +Minimum oppsett: + +- ```@KonfigVerdi(value = "min.property") String minProperty``` +- ```@KonfigVerdi(value = "min.property", required=false) String minProperty``` - kaster ikke exception om verdien ikke finnes. +- ```@KonfigVerdi(value = "min.property", defaultVeidi="default konfig verdi") String minProperty``` - returnerer default verdi om verdien ikke finnes i konfig. +- ``` + @KonfigVerdi(value = "min.property") + private String minProperty + +Det er mulig å hente konfig direkte fra koden ved å kalle `getProperty` eller `getRequiredProperty` fra `Environment` klassen: + +- ```Environment.current().getProperty("min.property")``` - returnerer en String eller null om det ikke finnes. +- ```Environment.current().getProperty("min.property", Integer.class)``` - returnerer en Integer, null om det ikke finnes eller Exception om ikke integer. +- ```Environment.current().getRequiredProperty("min.property")``` - returnerer en String, eller Exception om verdien ikke finnes. + +Følgende typer støttes og kan bli returnert: + +- String (default) +- Boolean / boolean +- Integer / int +- Period +- Duration +- LocalDate +- Long +- URI +- URL + +### Bruk eksempler + +- ```@KonfigVerdi(value = "test.enabled", required = false) boolean enabled``` == ```Environment.current().getProperty("test.enabled", integer.class)``` +- ```@KonfigVerdi(value = "bruker.navn" String bruker``` == ```Environment.current().getProperty("bruker.navn")``` +- ```@KonfigVerdi(value = "periode.fp") Period periode``` == ```Environment.current().getProperty("periode.fp", Period.class)``` +- ```@KonfigVerdi(value = HENDELSE_BASE_ENDPOINT, defaultValue=DEFAULT_BASE_ENDPOINT) URI baseEndpoint``` == ```Environment.current().getProperty(HENDELSE_BASE_ENDPOINT, URI.class, DEFAULT_BASE_ENDPOINT)``` + +### Utilities + +- `Environment` - statisk klasse som gir informasjon om miljøet appen kjører i. +- `Cluster` - statisk klasse med info om clusteret appen kjører i f.eks: isProd(), isDev(), isLocal(), etc. +- `Namespace` - statisk klasse som leverer egenskaper om namespacet appen kjører i f.eks: getName() +- `Application` - statisk klasse som leverer injisert navn på applikasjon: getName() +- `ClienId` - statisk klasse som leverer egenskaper om clientId for appen: getClientId() + +### Lisens + +MIT diff --git a/felles/konfig/pom.xml b/felles/konfig/pom.xml new file mode 100644 index 000000000..912847ec9 --- /dev/null +++ b/felles/konfig/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + felles + no.nav.foreldrepenger.felles + 0.0.0-SNAPSHOT + + + felles-konfig + + + + jakarta.enterprise + jakarta.enterprise.cdi-api + provided + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + prod-fss + default + + + + + + + + diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Application.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Application.java new file mode 100644 index 000000000..962cb6be5 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Application.java @@ -0,0 +1,32 @@ +package no.nav.foreldrepenger.konfig; + +import static java.lang.System.getenv; + +import java.util.Optional; + +public class Application { + + private final String name; + + private Application(String name) { + this.name = name; + } + + public static Application of(String name) { + return new Application(name); + } + + public String getName() { + return name; + } + + public static Application current() { + return Application.of(Optional.ofNullable(getenv(NaisProperty.APPLICATION.propertyName())).orElse("vtp")); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[application=" + name + "]"; + } + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/ApplicationPropertiesKonfigProvider.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/ApplicationPropertiesKonfigProvider.java new file mode 100644 index 000000000..ce3826305 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/ApplicationPropertiesKonfigProvider.java @@ -0,0 +1,104 @@ +package no.nav.foreldrepenger.konfig; + +import static java.lang.System.getenv; +import static no.nav.foreldrepenger.konfig.StandardPropertySource.APP_PROPERTIES; + +import java.io.IOException; +import java.util.Optional; +import java.util.Properties; + +import jakarta.enterprise.context.Dependent; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import no.nav.foreldrepenger.konfig.KonfigVerdi.Converter; + +@Dependent +public class ApplicationPropertiesKonfigProvider extends PropertiesKonfigVerdiProvider { + + static class Init { + // lazy init singleton + static final Properties PROPS = lesFra(); + private static final String SUFFIX = ".properties"; + private static final String PREFIX = "application"; + + private Init() { + } + + private static Properties lesFra() { + var c = new Properties(); + lesFra(namespaceKonfig(), lesFra(clusterKonfig(), lesFra("", new Properties()))) + .forEach((k, v) -> c.put(k.toString().toLowerCase(), v.toString())); + return c; + } + + private static Properties lesFra(String infix, Properties p) { + if (infix == null) { + return p; + } + String navn = PREFIX + infix + SUFFIX; + try (var is = ApplicationPropertiesKonfigProvider.class.getClassLoader().getResourceAsStream(navn)) { + if (is != null) { + LOG.info("Laster properties fra {}", navn); + p.load(is); + return p; + } + } catch (IOException e) { + LOG.info("Propertyfil {} ikke lesbar", navn); + } + LOG.info("Propertyfil {} ikke funnet", navn); + return p; + } + + private static String namespaceKonfig() { + var namespaceName = namespaceName(); + if (namespaceName != null) { + return clusterKonfig() + "-" + namespaceName; + } else { + var appName = System.getProperty("app.name"); + if (appName != null) { + return clusterKonfig() + "-" + appName; + } + } + return null; + } + + private static String namespaceName() { + return getenv(NaisProperty.NAMESPACE.propertyName()); + } + + private static String clusterKonfig() { + return "-" + clusterName(); + } + + private static String clusterName() { + return Optional.ofNullable(getenv(NaisProperty.CLUSTER.propertyName())) + .orElse(Cluster.VTP.clusterName()); + } + } + + private static final int PRIORITET = EnvPropertiesKonfigVerdiProvider.PRIORITET + 1; + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationPropertiesKonfigProvider.class); + + public ApplicationPropertiesKonfigProvider() { + super(Init.PROPS, APP_PROPERTIES); + } + + @Override + public V getVerdi(String key, Converter converter) { + return Optional.ofNullable(super.getVerdi(key.toLowerCase(), converter)) + .orElse(null); + } + + @Override + public boolean harVerdi(String key) { + return super.harVerdi(key.toLowerCase()); + } + + @Override + public int getPrioritet() { + return PRIORITET; + } +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/ClientId.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/ClientId.java new file mode 100644 index 000000000..ab1a1162e --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/ClientId.java @@ -0,0 +1,44 @@ +package no.nav.foreldrepenger.konfig; + +import static java.lang.System.getenv; + +public class ClientId { + + private static final String DELIMIT = ":"; + + private final String id; + + private ClientId(String clientId) { + this.id = clientId; + } + + public static ClientId of(String clientId) { + return new ClientId(clientId); + } + + public static ClientId of(Cluster cluster, Namespace namespace, Application application) { + return of(cluster.clusterName(), namespace.getName(), application.getName()); + } + + public static ClientId of(Cluster cluster, Namespace namespace, String application) { + return of(cluster.clusterName(), namespace.getName(), application); + } + + private static ClientId of(String cluster, String namespace, String application) { + return of(cluster + DELIMIT + namespace + DELIMIT + application); + } + + public String getClientId() { + return id; + } + + public static ClientId current() { + return ClientId.of(getenv(NaisProperty.CLIENTID.propertyName())); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[clientId=" + id + "]"; + } + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Cluster.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Cluster.java new file mode 100644 index 000000000..b883d1e65 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Cluster.java @@ -0,0 +1,78 @@ +package no.nav.foreldrepenger.konfig; + +import static java.lang.System.getenv; + +import java.util.Arrays; +import java.util.Objects; + +public enum Cluster { + VTP("vtp"), + DEV_FSS("dev-fss"), + DEV_GCP("dev-gcp"), + PROD_GCP("prod-gcp"), + PROD_FSS("prod-fss"); + + private static final String PROD = "prod"; + private static final String DEV = "dev"; + private static final String FSS = "fss"; + private static final String GCP = "gcp"; + + private final String name; + + Cluster(String name) { + this.name = name; + } + + public String clusterName() { + return name; + } + + public boolean isProd() { + return name.startsWith(PROD); + } + + public boolean isDev() { + return name.startsWith(DEV); + } + + boolean isVTP() { + return name.startsWith(VTP.name); + } + + boolean isFss() { + return name.endsWith(FSS); + } + + boolean isGcp() { + return name.endsWith(GCP); + } + + public boolean isLocal() { + return !isProd() && !isDev(); + } + + public boolean isSameClass(Cluster other) { + return name.substring(0, 2).equals(other.name.substring(0, 2)); + } + + public boolean isCoLocated(Cluster other) { + return name.substring(name.length() - 3).equals(other.name.substring(other.name.length() - 3)); + } + + public static Cluster current() { + var active = getenv(NaisProperty.CLUSTER.propertyName()); + return Arrays.stream(values()) + .filter(c -> active != null && Objects.equals(active, c.name)) + .findFirst() + .orElse(VTP); + } + + public static Cluster of(String name) { + return Arrays.stream(values()) + .filter(v -> v.name.equals(name)) + .findFirst() + .orElseThrow(); + } + + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/DefaultValueKonfigProvider.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/DefaultValueKonfigProvider.java new file mode 100644 index 000000000..55b5dd5d6 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/DefaultValueKonfigProvider.java @@ -0,0 +1,57 @@ +package no.nav.foreldrepenger.konfig; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +import no.nav.foreldrepenger.konfig.KonfigVerdi.Converter; + +public class DefaultValueKonfigProvider implements KonfigVerdiProvider { + + @Override + public PropertySourceMetaData getAllProperties() { + return new PropertySourceMetaData(StandardPropertySource.DEFAULT, new Properties()); + } + + @Override + public StandardPropertySource getSource() { + return StandardPropertySource.DEFAULT; + } + + @Override + public V getVerdi(String verdi, Converter converter) { + return converter.tilVerdi(verdi); + } + + @Override + public List getVerdier(String verdier, Converter converter) { + return Arrays.stream(verdier.split(",\\s*")) + .map(converter::tilVerdi) + .toList(); + + } + + @Override + public Map getVerdierAsMap(String verdier, Converter converter) { + return Arrays.stream(verdier.split(",\\s*")) + .map(s -> s.split(":\\s*")) + .collect( + Collectors.toMap( + e -> e[0], + e -> converter.tilVerdi(e[1]) + )); + } + + @Override + public boolean harVerdi(String key) { + return true; + } + + @Override + public int getPrioritet() { + return 0; + } + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/EnvPropertiesKonfigVerdiProvider.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/EnvPropertiesKonfigVerdiProvider.java new file mode 100644 index 000000000..0b4930f5f --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/EnvPropertiesKonfigVerdiProvider.java @@ -0,0 +1,53 @@ +package no.nav.foreldrepenger.konfig; + +import no.nav.foreldrepenger.konfig.KonfigVerdi.Converter; + +import jakarta.enterprise.context.Dependent; +import java.util.Optional; +import java.util.Properties; + +import static no.nav.foreldrepenger.konfig.StandardPropertySource.ENV_PROPERTIES; + +@Dependent +public class EnvPropertiesKonfigVerdiProvider extends PropertiesKonfigVerdiProvider { + + public static final int PRIORITET = SystemPropertiesKonfigVerdiProvider.PRIORITET + 1; + + static class Init { + // lazy init singleton + static final Properties ENV = getEnv(); + + private Init() { + } + + private static Properties getEnv() { + var p = new Properties(); + p.putAll(System.getenv()); + return p; + } + } + + public EnvPropertiesKonfigVerdiProvider() { + super(Init.ENV, ENV_PROPERTIES); + } + + @Override + public V getVerdi(String key, Converter converter) { + return Optional.ofNullable(super.getVerdi(key, converter)) + .orElse(super.getVerdi(upper(key), converter)); + } + + @Override + public boolean harVerdi(String key) { + return super.harVerdi(key) || super.harVerdi(upper(key)); + } + + private static String upper(String key) { + return key.toUpperCase().replace('.', '_'); + } + + @Override + public int getPrioritet() { + return PRIORITET; + } +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Environment.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Environment.java new file mode 100644 index 000000000..ee7f022c8 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Environment.java @@ -0,0 +1,264 @@ +package no.nav.foreldrepenger.konfig; + +import static java.lang.System.getenv; + +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URL; +import java.time.Duration; +import java.time.LocalDate; +import java.time.Period; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import no.nav.foreldrepenger.konfig.KonfigVerdi.*; + +public final class Environment { + + static final class Init { + // Josh Bloch's lazy load singleton (ref "Effective Java"). + // Siden Init ikke lastes før den referes blir feltet her initiert først når den aksesseres første gang. + static final Environment CURRENT = of(Cluster.current(), Namespace.current(), Application.current(), + ClientId.current(), currentImage()); + + private Init() { + } + + private static Environment of(Cluster cluster, Namespace namespace, Application application, + ClientId clientId, String image) { + var ensureClientId = Optional.ofNullable(clientId).orElseGet(() -> composeClientId(cluster, namespace, application)); + return new Environment(cluster, namespace, application, ensureClientId, image); + } + + private static ClientId composeClientId(Cluster cluster, Namespace namespace, Application application) { + return ClientId.of(cluster.clusterName() + ":" + namespace.getName() + ":" + application.getName()); + } + + private static String currentImage() { + return getenv(NaisProperty.IMAGE.propertyName()); + } + + } + + private final Cluster cluster; + private final Namespace namespace; + private final Application application; + private final ClientId clientId; + private final String imageName; + private final List propertySources; + + private Environment(Cluster cluster, Namespace namespace, Application application, ClientId clientId, String imageName) { + this.cluster = cluster; + this.namespace = namespace; + this.application = application; + this.clientId = clientId; + this.imageName = imageName; + this.propertySources = List.of( + new SystemPropertiesKonfigVerdiProvider(), + new EnvPropertiesKonfigVerdiProvider(), + new ApplicationPropertiesKonfigProvider()); + } + + public static Environment current() { + return Init.CURRENT; + } + + public Cluster getCluster() { + return cluster; + } + + public Namespace getNamespace() { + return namespace; + } + + public Application getApplication() { + return application; + } + + public ClientId getClientId() { + return clientId; + } + + public boolean isProd() { + return cluster.isProd(); + } + + public boolean isDev() { + return cluster.isDev(); + } + + public boolean isVTP() { + return cluster.isVTP(); + } + + public boolean isLocal() { + return cluster.isLocal(); + } + + public boolean isFss() { + return cluster.isFss(); + } + + public boolean isGcp() { + return cluster.isGcp(); + } + + public String clusterName() { + return cluster.clusterName(); + } + + public String namespace() { + return namespace.getName(); + } + + public String application() { + return application.getName(); + } + + public String clientId() { + return clientId.getClientId(); + } + + public String imageName() { + return imageName; + } + + public String getNaisAppName() { + return getProperty(NaisProperty.APPLICATION.propertyName(), "vtp"); + } + + public String getTruststorePath() { + return getenv(NaisProperty.TRUSTSTORE_PATH.propertyName()); + } + + public String getTruststorePassword() { + return getenv(NaisProperty.TRUSTSTORE_PASSWORD.propertyName()); + } + + public Properties getPropertiesWithPrefix(String prefix) { + Properties props = new Properties(); + props.putAll(getProperties(StandardPropertySource.SYSTEM_PROPERTIES).getVerdier()); + props.putAll(getProperties(StandardPropertySource.ENV_PROPERTIES).getVerdier()); + props.putAll(getProperties(StandardPropertySource.APP_PROPERTIES).getVerdier()); + + var filtered = new Properties(); + filtered.putAll(props.entrySet() + .stream() + .filter(k -> k.getKey().toString().startsWith(prefix)) + .collect( + Collectors.toMap( + e -> (String) e.getKey(), + e -> (String) e.getValue()))); + return filtered; + } + + public PropertySourceMetaData getProperties(StandardPropertySource source) { + return propertySources.stream() + .filter(p -> p.getSource().equals(source)) + .findFirst() + .map(KonfigVerdiProvider::getAllProperties) + .orElseThrow(); + } + + public List getPropertySources() { + return propertySources; + } + + public String getProperty(String key, String defaultVerdi) { + return getProperty(key, String.class, defaultVerdi); + } + + public T getProperty(String key, Class targetType) { + return getProperty(key, targetType, null); + } + + public String getRequiredProperty(String key) { + return getRequiredProperty(key, () -> new IllegalStateException(key + " ble ikke funnet")); + } + + public String getRequiredProperty(String key, Supplier exceptionSupplier) { + return Optional.ofNullable(getProperty(key)) + .orElseThrow(exceptionSupplier); + } + + public T getRequiredProperty(String key, Class targetType) { + return Optional.ofNullable(getProperty(key, targetType)) + .orElseThrow(() -> new IllegalStateException(key + " ble ikke funnet")); + } + + @SuppressWarnings("unchecked") + public T getProperty(String key, Class targetType, T defaultVerdi) { + var converter = converterFor(targetType); + if (converter == null && !targetType.equals(String.class)) { + throw new IllegalArgumentException("Konvertering til " + targetType + " er ikke støttet"); + } + + return propertySources.stream() + .filter(s -> s.harVerdi(key)) + .map(s -> s.getVerdi(key, converter)) + .filter(Objects::nonNull) + .findFirst() + .map(v -> (T) v) + .orElse(defaultVerdi); + } + + public String getProperty(String key) { + return getProperty(key, (String) null); + } + + private static Converter converterFor(Class targetType) { + try { + if (targetType.equals(String.class)) { + return construct(NoConverter.class); + } + if (targetType.equals(Period.class)) { + return construct(PeriodConverter.class); + } + if (targetType.equals(Duration.class)) { + return construct(DurationConverter.class); + } + if (targetType.equals(LocalDate.class)) { + return construct(LocalDateConverter.class); + } + if (targetType.equals(Long.class)) { + return construct(LongConverter.class); + } + if (targetType.equals(Boolean.class) || targetType.equals(boolean.class)) { + return construct(BooleanConverter.class); + } + if (targetType.equals(URI.class)) { + return construct(UriConverter.class); + } + if (targetType.equals(URL.class)) { + return construct(UrlConverter.class); + } + if (targetType.equals(Integer.class) || targetType.equals(int.class)) { + return construct(IntegerConverter.class); + } + if (targetType.equals(Long.class) || targetType.equals(long.class)) { + return construct(LongConverter.class); + } + return null; + } catch (Exception e) { + throw new IllegalArgumentException("Uventet feil ved konstruksjon av konverter for " + targetType); + } + } + + private static Converter construct(Class> clazz) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + return clazz.getDeclaredConstructor().newInstance(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[cluster=" + cluster + + ", namespace=" + namespace + + ", propertySources=" + propertySources + + "]"; + } +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdi.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdi.java new file mode 100644 index 000000000..01c2f1cd9 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdi.java @@ -0,0 +1,178 @@ +package no.nav.foreldrepenger.konfig; + +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; +import java.lang.annotation.*; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.time.Duration; +import java.time.LocalDate; +import java.time.Period; +import java.time.format.DateTimeParseException; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) +public @interface KonfigVerdi { + + Annotation TYPE_LITERAL = new KonfigVerdiTypeLiteral(); + + /* Nøkkel for å slå opp verdi. */ + @Nonbinding + String value() default ""; + + @Nonbinding + boolean required() default true; + + @Nonbinding + String defaultVerdi() default ""; + + @Nonbinding + String beskrivelse() default ""; + + @Nonbinding + Class> converter() default NoConverter.class; // NOSONAR + + interface Converter { + V tilVerdi(String verdi); + } + + class NoConverter implements Converter { + + @Override + public String tilVerdi(String verdi) { + return verdi; + } + + } + + class BooleanConverter implements Converter { + + @Override + public Boolean tilVerdi(String verdi) { + return verdi == null ? Boolean.FALSE : Boolean.valueOf(verdi); + } + } + + class IntegerConverter implements Converter { + + @Override + public Integer tilVerdi(String verdi) { + return verdi == null ? null : Integer.valueOf(verdi); + } + } + + class LongConverter implements Converter { + + @Override + public Long tilVerdi(String verdi) { + return verdi == null ? null : Long.valueOf(verdi); + } + } + + class UriConverter implements Converter { + + @Override + public URI tilVerdi(String verdi) { + try { + return verdi == null ? null : new URI(verdi); + } catch (URISyntaxException e) { + throw new IllegalStateException( + "Ugyldig konfigurasjonsparameter, kan ikke konvertere til java.net.URI: " + verdi, e); + } + } + } + + class UrlConverter implements Converter { + + @Override + public URL tilVerdi(String verdi) { + try { + return verdi == null ? null : new URL(verdi); + } catch (MalformedURLException e) { + throw new IllegalStateException( + "Ugyldig konfigurasjonsparameter, kan ikke konvertere til java.net.URL: " + verdi, e); + } + } + } + + class PeriodConverter implements Converter { + + @Override + public Period tilVerdi(String verdi) { + try { + return verdi == null ? null : Period.parse(verdi); + } catch (DateTimeParseException e) { + throw new IllegalStateException( + "Ugyldig konfigurasjonsparameter, kan ikke konvertere til java.time.Period: " + verdi, e); + } + } + } + + class DurationConverter implements Converter { + + @Override + public Duration tilVerdi(String verdi) { + try { + return verdi == null ? null : Duration.parse(verdi); + } catch (DateTimeParseException e) { + throw new IllegalStateException( + "Ugyldig konfigurasjonsparameter, kan ikke konvertere til java.time.Duration: " + verdi, e); + } + } + } + + class LocalDateConverter implements Converter { + + @Override + public LocalDate tilVerdi(String verdi) { + try { + return verdi == null ? null : LocalDate.parse(verdi); + } catch (DateTimeParseException e) { + throw new IllegalStateException( + "Ugyldig konfigurasjonsparameter, kan ikke konvertere til java.time.LocalDate: " + verdi, e); + } + } + } + + class StringDuplicator implements Converter { + + @Override + public String tilVerdi(String verdi) { + return verdi + verdi; + } + } + + class KonfigVerdiTypeLiteral extends AnnotationLiteral implements KonfigVerdi { + + @Override + public String value() { + return ""; + } + + @Override + public String defaultVerdi() { + return ""; + } + + @Override + public boolean required() { + return false; + } + + @Override + public String beskrivelse() { + return ""; + } + + @Override + public Class> converter() { + return NoConverter.class; + } + } +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProdusent.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProdusent.java new file mode 100644 index 000000000..c252d1656 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProdusent.java @@ -0,0 +1,267 @@ +package no.nav.foreldrepenger.konfig; + +import jakarta.annotation.PostConstruct; +import no.nav.foreldrepenger.konfig.KonfigVerdi.Converter; +import no.nav.foreldrepenger.konfig.KonfigVerdiProviderOutput.ProviderOutput; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.inject.Inject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.Period; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.regex.Pattern; + +/* Producer av konfig verdier. Støtter pluggbart antall providere av konfigurasjonsverdier. */ +@ApplicationScoped +public class KonfigVerdiProdusent { + private static final DefaultValueKonfigProvider DEFAULTVALUEPROVIDER = new DefaultValueKonfigProvider(); + private static final Pattern SKJUL = Pattern.compile(".*(passw?ord|[k|c]redential|secret).*"); // NOSONAR + private static final Logger log = LoggerFactory.getLogger(KonfigVerdiProdusent.class); + + private Instance providerBeans; + + private List providers = new ArrayList<>(); + + private Set konfigVerdiReferanser = new ConcurrentSkipListSet<>(); + + @SuppressWarnings("rawtypes") + private Map, KonfigVerdi.Converter> converters = new ConcurrentHashMap<>(); + + KonfigVerdiProdusent() { + // for CDI proxy + } + + @Inject + public KonfigVerdiProdusent(@Any Instance providerBeans) { + this.providerBeans = providerBeans; + } + + @KonfigVerdi + @Produces + public String getKonfigVerdiString(InjectionPoint ip) { + Object verdi = getEnkelVerdi(ip); + return verdi == null ? null : String.valueOf(verdi); + } + + @KonfigVerdi + @Produces + public Boolean getKonfigVerdiBoolean(final InjectionPoint ip) { + Object verdi = getEnkelVerdi(ip); + if (verdi == null) { + return null; // NOSONAR + } + return verdi instanceof Boolean value ? value : Boolean.parseBoolean((String) verdi); + } + + @KonfigVerdi + @Produces + public Integer getKonfigVerdiInteger(final InjectionPoint ip) { + Object verdi = getEnkelVerdi(ip); + if (verdi == null) { + return null; + } + return verdi instanceof Integer value ? value : Integer.valueOf((String) verdi); + } + + @KonfigVerdi + @Produces + public Period getKonfigVerdiPeriod(final InjectionPoint ip) { + Object verdi = getEnkelVerdi(ip); + if (verdi == null) { + return null; + } + return verdi instanceof Period value ? value : Period.parse((String) verdi); + } + + @KonfigVerdi + @Produces + public Duration getKonfigVerdiDuration(final InjectionPoint ip) { + Object verdi = getEnkelVerdi(ip); + if (verdi == null) { + return null; + } + return verdi instanceof Duration value ? value : Duration.parse((String) verdi); + } + + @KonfigVerdi + @Produces + public LocalDate getKonfigVerdiLocalDate(final InjectionPoint ip) { + Object verdi = getEnkelVerdi(ip); + if (verdi == null) { + return null; + } + return verdi instanceof LocalDate value ? value : LocalDate.parse((String) verdi); + } + + @KonfigVerdi + @Produces + public Long getKonfigVerdiLong(final InjectionPoint ip) { + Object verdi = getEnkelVerdi(ip); + if (verdi == null) { + return null; + } + return verdi instanceof Long value ? value : Long.valueOf((String) verdi); + } + + /* + * Støtter kun URI, ikke URL. Bør unngå URL som konfig verdier pga kjente + * problemer med hashcode/equals og ytelse etc. + */ + @KonfigVerdi + @Produces + public URI getKonfigVerdiUri(final InjectionPoint ip) { + Object verdi = getEnkelVerdi(ip); + try { + if (verdi == null) { + return null; + } + return verdi instanceof URI value ? value : new URI((String) verdi); + } catch (URISyntaxException e) { + throw new IllegalStateException("KonfigVerdi [" + verdi + "] er ikke en java.net.URI", e); + } + } + + /* + * Returnerer Liste av verdier. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + @KonfigVerdi + @Produces + public List getKonfigVerdiList(final InjectionPoint ip) { + KonfigVerdi annotation = getAnnotation(ip); + String key = annotation.value(); + Converter converter = getConverter(annotation.converter()); + return getVerdi(ip, annotation, KonfigVerdiProviderOutput.LIST, key, converter); + } + + /* + * Returnerer Liste av verdier. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + @KonfigVerdi + @Produces + public Map getKonfigVerdiMap(final InjectionPoint ip) { + KonfigVerdi annotation = getAnnotation(ip); + String key = annotation.value(); + Converter converter = getConverter(annotation.converter()); + return getVerdi(ip, annotation, KonfigVerdiProviderOutput.MAP, key, converter); + } + + @SuppressWarnings("unchecked") + public Object getEnkelVerdi(final InjectionPoint ip) { + KonfigVerdi annotation = getAnnotation(ip); + return getVerdi(ip, annotation, KonfigVerdiProviderOutput.SIMPLE); + } + + @SuppressWarnings({"rawtypes"}) + protected T getVerdi(InjectionPoint ip, KonfigVerdi annotation, ProviderOutput outputFunction) { + String key = annotation.value(); + Converter converter = getConverter(annotation.converter()); + return getVerdi(ip, annotation, outputFunction, key, converter); + + } + + @SuppressWarnings("rawtypes") + public T getVerdi(InjectionPoint ip, KonfigVerdi annotation, ProviderOutput outputFunction, String key, + Converter converter) { + for (KonfigVerdiProvider kvp : providers) { + try { + if (kvp.harVerdi(key)) { + T output = outputFunction.getOutput(kvp, key, converter); + sporKonfigVerdier(ip, annotation, output); + return output; + } + } catch (RuntimeException e) { + throw new IllegalStateException( + "Kunne ikke slå opp verdi for key [" + key + "] fra " + kvp.getClass().getName() + + "; InjectionPoint=" + ip, + e); + } + } + String defaultVerdi = annotation.defaultVerdi(); + if (annotation.required() && defaultVerdi.isEmpty()) { + throw new IllegalStateException( + "Mangler verdi for key(required): " + annotation.value() + "; InjectionPoint=" + ip); + } else { + if (!defaultVerdi.isEmpty()) { + T output = outputFunction.getOutput(DEFAULTVALUEPROVIDER, defaultVerdi, converter); + sporKonfigVerdier(ip, annotation, output); + return output; + } + } + return null; + } + + public void sporKonfigVerdier(InjectionPoint ip, KonfigVerdi annot, T output) { + + Member member = ip.getMember(); + String name = Constructor.class.isAssignableFrom(member.getClass()) + ? member.getName() + : member.getDeclaringClass().getName() + "#" + member.getName(); + if (!konfigVerdiReferanser.contains(name)) { + String key = annot.value(); + Object val = SKJUL.matcher(key).matches() + ? "********* (skjult)" + : output; + konfigVerdiReferanser.add(name); + log.info("{}: {}=\"{}\" @{}", KonfigVerdi.class.getSimpleName(), key, val, name); + } + } + + @SuppressWarnings("rawtypes") + private KonfigVerdi.Converter getConverter(Class> converterClass) { + KonfigVerdi.Converter converter = converters.get(converterClass); + if (converter == null) { + try { + converter = converterClass.getDeclaredConstructor().newInstance(); + converters.put(converterClass, converter); + } catch (ReflectiveOperationException e) { + throw new UnsupportedOperationException("Mangler no-arg constructor for klasse: " + converterClass, e); + } + } + return converter; + + } + + protected KonfigVerdi getAnnotation(final InjectionPoint ip) { + Annotated annotert = ip.getAnnotated(); + + if (annotert == null) { + throw new IllegalArgumentException("Mangler annotation KonfigVerdi for InjectionPoint=" + ip); + } + if (annotert.isAnnotationPresent(KonfigVerdi.class)) { + KonfigVerdi annotation = annotert.getAnnotation(KonfigVerdi.class); + if (!annotation.value().isEmpty()) { + return annotation; + } + } + throw new IllegalStateException("Mangler key. Kan ikke være tom eller null: " + ip.getMember()); + } + + @PostConstruct + public void init() { + List alleProviders = new ArrayList<>(); + for (KonfigVerdiProvider kvp : providerBeans) { + alleProviders.add(kvp); + } + Collections.sort(alleProviders, Comparator.comparingInt(KonfigVerdiProvider::getPrioritet)); + + providers.clear(); + providers.addAll(alleProviders); + } + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProvider.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProvider.java new file mode 100644 index 000000000..eae406a4f --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProvider.java @@ -0,0 +1,27 @@ +package no.nav.foreldrepenger.konfig; + +import java.util.List; +import java.util.Map; + +/** + * Provider som kan slå opp verdi for en angitt key + */ +public interface KonfigVerdiProvider { + + /* Get verdi for angitt key. */ + V getVerdi(String key, KonfigVerdi.Converter converter); + + List getVerdier(String key, KonfigVerdi.Converter converter); + + Map getVerdierAsMap(String key, KonfigVerdi.Converter converter); + + boolean harVerdi(String key); + + StandardPropertySource getSource(); + + /* Prioritet rekkefølge. 1 er høyest prioritet. */ + int getPrioritet(); + + PropertySourceMetaData getAllProperties(); + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProviderOutput.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProviderOutput.java new file mode 100644 index 000000000..54d5ceb43 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/KonfigVerdiProviderOutput.java @@ -0,0 +1,50 @@ +package no.nav.foreldrepenger.konfig; + +import no.nav.foreldrepenger.konfig.KonfigVerdi.Converter; + +import java.util.List; +import java.util.Map; + +class KonfigVerdiProviderOutput { + + @SuppressWarnings("rawtypes") + static final ProviderOutput SIMPLE = new VerdiOutput(); + @SuppressWarnings("rawtypes") + static final ProviderOutput LIST = new ListOutput(); + @SuppressWarnings("rawtypes") + static final ProviderOutput MAP = new MapOutput(); + + interface ProviderOutput { + T getOutput(KonfigVerdiProvider provider, String key, KonfigVerdi.Converter converter); + } + + @SuppressWarnings("rawtypes") + static class ListOutput implements ProviderOutput { + + @SuppressWarnings("unchecked") + @Override + public List getOutput(KonfigVerdiProvider provider, String key, Converter converter) { + return provider.getVerdier(key, converter); + } + } + + @SuppressWarnings("rawtypes") + static final class MapOutput implements ProviderOutput { + + @SuppressWarnings("unchecked") + @Override + public Map getOutput(KonfigVerdiProvider provider, String key, Converter converter) { + return provider.getVerdierAsMap(key, converter); + } + } + + @SuppressWarnings("rawtypes") + static class VerdiOutput implements ProviderOutput { + @SuppressWarnings("unchecked") + @Override + public Object getOutput(KonfigVerdiProvider provider, String key, Converter converter) { + return provider.getVerdi(key, converter); + } + } + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/NaisProperty.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/NaisProperty.java new file mode 100644 index 000000000..8dd4a4e04 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/NaisProperty.java @@ -0,0 +1,28 @@ +package no.nav.foreldrepenger.konfig; + +/** + * Standard navn på environment injisert av NAIS + * Med vilje ikke public slik at man heller går via Environment + */ +enum NaisProperty { + CLUSTER("NAIS_CLUSTER_NAME"), + NAMESPACE("NAIS_NAMESPACE"), + APPLICATION("NAIS_APP_NAME"), + CLIENTID("NAIS_CLIENT_ID"), // Format :: + IMAGE("NAIS_APP_IMAGE"), // Format /:- + + TRUSTSTORE_PATH("NAV_TRUSTSTORE_PATH"), + TRUSTSTORE_PASSWORD("NAV_TRUSTSTORE_PASSWORD"), + ; + + private final String name; + + NaisProperty(String name) { + this.name = name; + } + + String propertyName() { + return name; + } + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Namespace.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Namespace.java new file mode 100644 index 000000000..ebca66eb0 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/Namespace.java @@ -0,0 +1,39 @@ +package no.nav.foreldrepenger.konfig; + +import java.util.Optional; + +import static java.lang.System.getenv; + +public class Namespace { + + private static final String DEFAULT_NAMESPACE = "teamforeldrepenger"; + + private final String name; + + private Namespace(String name) { + this.name = name; + } + + public static Namespace of(String name) { + return new Namespace(name); + } + + public String getName() { + return name; + } + + public static Namespace current() { + return Namespace.of(Optional.ofNullable(getenv(NaisProperty.NAMESPACE.propertyName())) + .orElse(DEFAULT_NAMESPACE)); + } + + public static Namespace foreldrepenger() { + return Namespace.of(DEFAULT_NAMESPACE); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[namespace=" + name + "]"; + } + +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/PropertiesKonfigVerdiProvider.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/PropertiesKonfigVerdiProvider.java new file mode 100644 index 000000000..eaa2a10fe --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/PropertiesKonfigVerdiProvider.java @@ -0,0 +1,60 @@ +package no.nav.foreldrepenger.konfig; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +public abstract class PropertiesKonfigVerdiProvider implements KonfigVerdiProvider { + + private final PropertySourceMetaData metadata; + + protected PropertiesKonfigVerdiProvider(Properties props, StandardPropertySource source) { + this(new PropertySourceMetaData(source, props)); + } + + protected PropertiesKonfigVerdiProvider(PropertySourceMetaData metaData) { + this.metadata = metaData; + } + + @Override + public StandardPropertySource getSource() { + return metadata.getSource(); + } + + @Override + public PropertySourceMetaData getAllProperties() { + return metadata; + } + + @Override + public V getVerdi(String key, KonfigVerdi.Converter converter) { + return converter.tilVerdi((String) metadata.getVerdier().get(key)); + } + + @Override + public boolean harVerdi(String key) { + return metadata.getVerdier().containsKey(key); + } + + @Override + public List getVerdier(String key, KonfigVerdi.Converter converter) { + String verdiString = (String) metadata.getVerdier().get(key); + List asList = Arrays.asList(verdiString.split(",\\s*")); + return asList.stream().map(converter::tilVerdi).toList(); + } + + @Override + public Map getVerdierAsMap(String key, KonfigVerdi.Converter converter) { + String str = (String) metadata.getVerdier().get(key); + + return Arrays.stream(str.split(",\\s*")) + .map(s -> s.split(":\\s*")) + .collect( + Collectors.toMap( + e -> e[0], + e -> converter.tilVerdi(e[1]) + )); + } +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/PropertySourceMetaData.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/PropertySourceMetaData.java new file mode 100644 index 000000000..759fc1031 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/PropertySourceMetaData.java @@ -0,0 +1,27 @@ +package no.nav.foreldrepenger.konfig; + +import java.util.Properties; + +public class PropertySourceMetaData { + private final StandardPropertySource source; + private final Properties verdier; + + public PropertySourceMetaData(StandardPropertySource source, Properties verdier) { + this.source = source; + this.verdier = verdier; + + } + + public Properties getVerdier() { + return verdier; + } + + public StandardPropertySource getSource() { + return source; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[source=" + source + ", verdier=" + verdier + "]"; + } +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/StandardPropertySource.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/StandardPropertySource.java new file mode 100644 index 000000000..8304f0ca1 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/StandardPropertySource.java @@ -0,0 +1,21 @@ +package no.nav.foreldrepenger.konfig; + +public enum StandardPropertySource { + SYSTEM_PROPERTIES("System properties"), + ENV_PROPERTIES("Environment properties"), + APP_PROPERTIES("Application properties"), + APP_PROPERTIES_CLUSTER("Cluster-specific application properties"), + APP_PROPERTIES_CLUSTER_NAMESPACE("Cluster og namespace specific application properties"), + + DEFAULT("Default"); + + private final String name; + + StandardPropertySource(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/SystemPropertiesKonfigVerdiProvider.java b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/SystemPropertiesKonfigVerdiProvider.java new file mode 100644 index 000000000..c54af5fc5 --- /dev/null +++ b/felles/konfig/src/main/java/no/nav/foreldrepenger/konfig/SystemPropertiesKonfigVerdiProvider.java @@ -0,0 +1,22 @@ +package no.nav.foreldrepenger.konfig; + +import jakarta.enterprise.context.Dependent; + +import static no.nav.foreldrepenger.konfig.StandardPropertySource.SYSTEM_PROPERTIES; + +/** + * Henter properties fra {@link System#getProperties}. + */ +@Dependent +public class SystemPropertiesKonfigVerdiProvider extends PropertiesKonfigVerdiProvider { + public static final int PRIORITET = Integer.MIN_VALUE; + + public SystemPropertiesKonfigVerdiProvider() { + super(System.getProperties(), SYSTEM_PROPERTIES); + } + + @Override + public int getPrioritet() { + return PRIORITET; + } +} diff --git a/felles/konfig/src/main/resources/META-INF/beans.xml b/felles/konfig/src/main/resources/META-INF/beans.xml new file mode 100644 index 000000000..8c5870f12 --- /dev/null +++ b/felles/konfig/src/main/resources/META-INF/beans.xml @@ -0,0 +1,6 @@ + + + diff --git a/felles/konfig/src/test/java/no/nav/foreldrepenger/konfig/EnvironmentTest.java b/felles/konfig/src/test/java/no/nav/foreldrepenger/konfig/EnvironmentTest.java new file mode 100644 index 000000000..099d460d8 --- /dev/null +++ b/felles/konfig/src/test/java/no/nav/foreldrepenger/konfig/EnvironmentTest.java @@ -0,0 +1,104 @@ +package no.nav.foreldrepenger.konfig; + +import static no.nav.foreldrepenger.konfig.Cluster.PROD_FSS; +import static no.nav.foreldrepenger.konfig.Cluster.PROD_GCP; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.PrintStream; +import java.net.URI; +import java.time.Duration; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class EnvironmentTest { + + private static final Logger LOG = LoggerFactory.getLogger(EnvironmentTest.class); + private static final Environment ENV = Environment.current(); + private static final PrintStream SYSOUT = System.out; + + @AfterEach + void after() { + System.setOut(SYSOUT); + } + + @Test + @EnabledIfEnvironmentVariable(named = "mvn", matches = "true") + void testEnvironment() { + assertEquals(PROD_FSS, ENV.getCluster()); + assertEquals("default", ENV.namespace()); + assertTrue(ENV.isProd()); + } + + @Test + void testCoLocated() { + assertTrue(Cluster.VTP.isCoLocated(Cluster.VTP)); + assertTrue(Cluster.DEV_GCP.isCoLocated(Cluster.DEV_GCP)); + assertTrue(PROD_FSS.isCoLocated(PROD_FSS)); + assertFalse(PROD_FSS.isCoLocated(Cluster.VTP)); + assertFalse(PROD_FSS.isCoLocated(Cluster.PROD_GCP)); + } + + @Test + void testClass() { + assertTrue(Cluster.VTP.isSameClass(Cluster.VTP)); + assertTrue(Cluster.DEV_GCP.isSameClass(Cluster.DEV_GCP)); + assertTrue(PROD_FSS.isSameClass(PROD_GCP)); + assertFalse(PROD_FSS.isSameClass(Cluster.VTP)); + assertFalse(Cluster.DEV_FSS.isSameClass(PROD_FSS)); + } + + @Test + void testURI() { + assertEquals(URI.create("http://www.nav.no"), ENV.getRequiredProperty("NAV", URI.class)); + } + + @Test + void testUppercase() { + assertEquals(PROD_FSS.clusterName(), ENV.getProperty("nais.cluster.name")); + } + + @Test + void testDuration() { + assertEquals(Duration.ofDays(42), ENV.getProperty("duration.property", Duration.class)); + assertEquals(Duration.ofDays(2), + ENV.getProperty("ikke.funnet", Duration.class, Duration.ofDays(2))); + } + + @Test + void testString() { + assertEquals("42", ENV.getProperty("finnes.ikke", "42")); + assertNull(ENV.getProperty("finnes.ikke")); + } + + @Test + void testBoolean() { + assertTrue(ENV.getProperty("test4.boolean", boolean.class)); + assertTrue(ENV.getProperty("test4.boolean", Boolean.class)); + } + + @Test + void testInt() { + LOG.info("Application property verdier {}", ENV.getProperties(StandardPropertySource.APP_PROPERTIES)); + assertEquals(Integer.valueOf(10), ENV.getProperty("test2.intproperty", Integer.class)); + assertEquals(Integer.valueOf(10), ENV.getProperty("test2.intproperty", int.class)); + } + + @Test + void testPropertiesFraEnvUkjentConverter() { + assertThrows(IllegalArgumentException.class, () -> ENV.getProperty("finnes.ikke", Double.class)); + } + + @Test + void testPropertiesIkkeFunnet() { + assertThrows(IllegalStateException.class, () -> ENV.getRequiredProperty("finnes.ikke")); + } + +} diff --git a/felles/konfig/src/test/java/no/nav/foreldrepenger/konfig/KonfigVerdiTest.java b/felles/konfig/src/test/java/no/nav/foreldrepenger/konfig/KonfigVerdiTest.java new file mode 100644 index 000000000..059ca66f5 --- /dev/null +++ b/felles/konfig/src/test/java/no/nav/foreldrepenger/konfig/KonfigVerdiTest.java @@ -0,0 +1,96 @@ +package no.nav.foreldrepenger.konfig; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import java.net.URI; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +class KonfigVerdiTest { + + private static final String NAV = "http://www.nav.no"; + private static final String KEY = "my.test.property"; + private static final String VALUE = "key1:true,key2:false"; + + private static final String KEY_INT = "my.test.property.age"; + private static final String VALUE_INT = "39"; + + private static final String KEY_BOOLEAN = "my.test.property.alenemor"; + private static final String VALUE_BOOLEAN = "false"; + + private static final String KEY_LOCAL_DATE = "my.local.date.test"; + private static final String VALUE_LOCAL_DATE = "1989-09-29"; + private static final Environment ENV = Environment.current(); + + private final int propFraFil = ENV.getProperty("TEST.proPerty", int.class); + private final int propFraFilOverride = ENV.getProperty("test1.property", int.class); + private final int systemVinner = ENV.getProperty("test2.property", int.class); + private final int namespaceVerdi = ENV.getProperty("test3.property", int.class); + private final String javaHome = ENV.getProperty("user.home"); + private final String myProperty = ENV.getProperty(KonfigVerdiTest.KEY); + private final Integer myIntegerPropertyValue = ENV.getProperty(KonfigVerdiTest.KEY_INT, Integer.class); + private final Boolean myBooleanPropertyValue = ENV.getProperty(KonfigVerdiTest.KEY_BOOLEAN, Boolean.class); + private final LocalDate myLocalDateValue = ENV.getProperty(KonfigVerdiTest.KEY_LOCAL_DATE, LocalDate.class); + private final String stringProperty = ENV.getProperty("my.property", "42"); + private final Integer intDefaultProperty = ENV.getProperty("my.property", Integer.class, 42); + private final boolean booleanDefaultProperty = ENV.getProperty("my.property", boolean.class, true); + private final URI uriDefaultProperty = ENV.getProperty("my.property", URI.class, URI.create(NAV)); + private final int defaultNotUsed = ENV.getProperty("my.property.notdefault", int.class, 42); + + @BeforeAll + public static void setupSystemPropertyForTest() { + System.setProperty("my.property.notdefault", "0"); + System.setProperty(KEY, VALUE); + System.setProperty(KEY_INT, VALUE_INT); + System.setProperty(KEY_BOOLEAN, VALUE_BOOLEAN); + System.setProperty(KEY_LOCAL_DATE, VALUE_LOCAL_DATE); + System.setProperty("test2.property", "50"); + } + + @Test + @EnabledIfEnvironmentVariable(named = "mvn", matches = "true") + void propertyFil() { + assertEquals(42, propFraFil); + assertEquals(42, propFraFil); + assertEquals(200, propFraFilOverride); + assertEquals(50, systemVinner); + assertEquals(10, namespaceVerdi); + } + + @Test + void defaultVerdier() { + assertEquals(0, defaultNotUsed); + assertEquals("42", stringProperty); + assertTrue(booleanDefaultProperty); + assertEquals(42, intDefaultProperty); + assertEquals(URI.create(NAV), uriDefaultProperty); + } + + @Test + void skal_injisere_konfig() { + assertNotNull(javaHome); + } + + @Test + void skal_injisere_verdi_fra_systemproperties() { + assertEquals(VALUE, myProperty); + } + + @Test + void skal_injisere_integer_fra_systemproperties() { + assertEquals(39, myIntegerPropertyValue); + } + + @Test + void skal_injisere_boolean_fra_systemproperties() { + assertFalse(myBooleanPropertyValue); + } + + @Test + void skal_injisere_local_date_fra_systemproperties() { + assertEquals(LocalDate.of(1989, 9, 29), myLocalDateValue); + } +} diff --git a/felles/konfig/src/test/resources/application-prod-fss-default.properties b/felles/konfig/src/test/resources/application-prod-fss-default.properties new file mode 100644 index 000000000..4f4b79a3e --- /dev/null +++ b/felles/konfig/src/test/resources/application-prod-fss-default.properties @@ -0,0 +1 @@ +test3.property=10 \ No newline at end of file diff --git a/felles/konfig/src/test/resources/application-prod-fss.properties b/felles/konfig/src/test/resources/application-prod-fss.properties new file mode 100644 index 000000000..891d8efe3 --- /dev/null +++ b/felles/konfig/src/test/resources/application-prod-fss.properties @@ -0,0 +1,3 @@ +test1.property=200 +test2.intproperty=10 +test3.property=10000 \ No newline at end of file diff --git a/felles/konfig/src/test/resources/application.properties b/felles/konfig/src/test/resources/application.properties new file mode 100644 index 000000000..f24cfdd13 --- /dev/null +++ b/felles/konfig/src/test/resources/application.properties @@ -0,0 +1,6 @@ +test.property=42 +test1.property=100 +test2.property=2000 +test3.property=10000000 +test4.boolean=true +duration.property=PT1008H diff --git a/felles/kontekst/pom.xml b/felles/kontekst/pom.xml index 156d8f8cc..f6ecb09dc 100644 --- a/felles/kontekst/pom.xml +++ b/felles/kontekst/pom.xml @@ -20,8 +20,8 @@ jakarta.enterprise.cdi-api - no.nav.foreldrepenger - konfig + no.nav.foreldrepenger.felles + felles-konfig no.nav.foreldrepenger.felles diff --git a/felles/log/pom.xml b/felles/log/pom.xml index 5440eab7f..64f501f97 100644 --- a/felles/log/pom.xml +++ b/felles/log/pom.xml @@ -18,8 +18,8 @@ jakarta.enterprise.cdi-api - no.nav.foreldrepenger - konfig + no.nav.foreldrepenger.felles + felles-konfig io.micrometer diff --git a/felles/oidc/pom.xml b/felles/oidc/pom.xml index 38cde811a..367d2a66b 100644 --- a/felles/oidc/pom.xml +++ b/felles/oidc/pom.xml @@ -39,8 +39,8 @@ felles-util - no.nav.foreldrepenger - konfig + no.nav.foreldrepenger.felles + felles-konfig org.bitbucket.b_c diff --git a/felles/pom.xml b/felles/pom.xml index 6b03a50ce..062133095 100644 --- a/felles/pom.xml +++ b/felles/pom.xml @@ -14,6 +14,7 @@ testutilities + konfig sikkerhet oidc feil @@ -107,6 +108,12 @@ ${project.version} + + no.nav.foreldrepenger.felles + felles-konfig + ${project.version} + + diff --git a/felles/sikkerhet/pom.xml b/felles/sikkerhet/pom.xml index d0d33baa5..26032ba05 100644 --- a/felles/sikkerhet/pom.xml +++ b/felles/sikkerhet/pom.xml @@ -33,8 +33,8 @@ felles-oidc - no.nav.foreldrepenger - konfig + no.nav.foreldrepenger.felles + felles-konfig jakarta.ws.rs diff --git a/integrasjon/kafka-properties/pom.xml b/integrasjon/kafka-properties/pom.xml index 139784049..1f9934e2e 100644 --- a/integrasjon/kafka-properties/pom.xml +++ b/integrasjon/kafka-properties/pom.xml @@ -15,8 +15,9 @@ - no.nav.foreldrepenger - konfig + no.nav.foreldrepenger.felles + felles-konfig + ${project.version} org.apache.kafka diff --git a/integrasjon/pom.xml b/integrasjon/pom.xml index 25c752b18..22912464c 100644 --- a/integrasjon/pom.xml +++ b/integrasjon/pom.xml @@ -28,7 +28,7 @@ - 5.8.0 + 5.9.0 diff --git a/integrasjon/rest-klient/pom.xml b/integrasjon/rest-klient/pom.xml index 5e0f0904a..3827dcff0 100644 --- a/integrasjon/rest-klient/pom.xml +++ b/integrasjon/rest-klient/pom.xml @@ -21,8 +21,9 @@ jakarta.ws.rs-api - no.nav.foreldrepenger - konfig + no.nav.foreldrepenger.felles + felles-konfig + ${project.version} no.nav.foreldrepenger.felles diff --git a/pom.xml b/pom.xml index 087119be8..ed0cf2083 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ no.nav.foreldrepenger.felles fp-bom - 1.0.5 + 2.0.0 felles-root @@ -18,7 +18,7 @@ felles integrasjon - + ${project.artifactId} @@ -33,7 +33,7 @@ no.nav.foreldrepenger.felles fp-bom - 1.0.5 + 2.0.0 import pom