diff --git a/.gitignore b/.gitignore index b3f19fd..4398718 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ hs_err_pid* /.project /doc/ /property-inject.launch + +/.idea \ No newline at end of file diff --git a/pom.xml b/pom.xml index e75d688..61ff49b 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,12 @@ 5.9.2 test + + org.junit.platform + junit-platform-runner + 1.9.2 + test + org.mockito mockito-junit-jupiter diff --git a/src/main/java/io/xlate/inject/PropertyFactory.java b/src/main/java/io/xlate/inject/PropertyFactory.java index ac921a4..9cf74a1 100644 --- a/src/main/java/io/xlate/inject/PropertyFactory.java +++ b/src/main/java/io/xlate/inject/PropertyFactory.java @@ -21,10 +21,7 @@ import java.io.InputStream; import java.lang.reflect.Executable; import java.lang.reflect.Member; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.net.URLStreamHandler; +import java.net.*; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -52,32 +49,46 @@ URLStreamHandler classPathHandler(Class beanType) { return new ClasspathURLStreamHandler(beanType.getClassLoader()); } + URL getResourceUrl(String filename) throws MalformedURLException { + return getResourceUrlByLocation(filename, this.getClass()); + } + URL getResourceUrl(PropertyResource annotation, Class beanType) throws MalformedURLException { - final String location = annotation.value(); - final URL resourceUrl; + String location = annotation.value(); + if (location.isEmpty()) { - StringBuilder resourceName = new StringBuilder(CLASSPATH); - resourceName.append(':'); - resourceName.append(beanType.getName().replace('.', '/')); - resourceName.append(".properties"); - resourceUrl = new URL(null, resourceName.toString(), classPathHandler(beanType)); + StringBuilder resourceName = new StringBuilder(CLASSPATH); + resourceName.append(':'); + resourceName.append(beanType.getName().replace('.', '/')); + resourceName.append(".properties"); + return new URL(null, resourceName.toString(), classPathHandler(beanType)); } else { - final String resolvedLocation; + String resolvedLocation; if (annotation.resolveEnvironment()) { resolvedLocation = replaceEnvironmentReferences(location); } else { resolvedLocation = location; } + return getResourceUrlByLocation(resolvedLocation, beanType); + } + } + private URL getResourceUrlByLocation(String location,Class beanType) throws MalformedURLException { + URL resourceUrl; try { - final URI resourceId = URI.create(resolvedLocation); + final URI resourceId; + if (location.indexOf(':')>0 && !location.startsWith(CLASSPATH)) { // we assume it is an url + resourceId = new URL(location).toURI(); + } else { + resourceId = URI.create(location); + } final String scheme = resourceId.getScheme(); if (scheme != null) { if (CLASSPATH.equals(scheme)) { - resourceUrl = new URL(null, resolvedLocation, classPathHandler(beanType)); + resourceUrl = new URL(null, location, classPathHandler(beanType)); } else { resourceUrl = resourceId.toURL(); } @@ -86,8 +97,10 @@ URL getResourceUrl(PropertyResource annotation, Class beanType) throws Malfor } } catch (IllegalArgumentException | MalformedURLException e) { throw new InjectionException(e); + } catch (URISyntaxException e) { + throw new InjectionException(e); } - } + return resourceUrl; } diff --git a/src/main/java/io/xlate/inject/PropertyFileProvider.java b/src/main/java/io/xlate/inject/PropertyFileProvider.java new file mode 100644 index 0000000..0096a33 --- /dev/null +++ b/src/main/java/io/xlate/inject/PropertyFileProvider.java @@ -0,0 +1,7 @@ +package io.xlate.inject; + +public interface PropertyFileProvider { + + String getLocation(); + +} diff --git a/src/main/java/io/xlate/inject/PropertyProducerBean.java b/src/main/java/io/xlate/inject/PropertyProducerBean.java index cefa657..06e7acf 100644 --- a/src/main/java/io/xlate/inject/PropertyProducerBean.java +++ b/src/main/java/io/xlate/inject/PropertyProducerBean.java @@ -33,8 +33,10 @@ import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.inject.InjectionException; +import javax.enterprise.inject.Instance; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; @@ -48,6 +50,9 @@ public class PropertyProducerBean { private final PropertyFactory factory = new PropertyFactory(); + @Inject + private Instance propertyFilenameProvider; + @Produces @Dependent @Property @@ -219,15 +224,22 @@ String getProperty(InjectionPoint point) throws IOException { return systemProperty; } + final boolean hasGlobalFile = propertyFilenameProvider != null && propertyFilenameProvider.isResolvable(); + final PropertyResource resource = annotation.resource(); - final String value; - final URL resourceUrl = factory.getResourceUrl(resource, beanType); - value = factory.getProperty(resourceUrl, resource.format(), resource.allowMissingResource(), propertyName, defaultValue); + String value; + URL resourceUrl = factory.getResourceUrl(resource, beanType); + value = factory.getProperty(resourceUrl, resource.format(), hasGlobalFile ? true : resource.allowMissingResource(), propertyName, defaultValue); if (value != null && annotation.resolveEnvironment()) { return factory.replaceEnvironmentReferences(value); } + if (value == null && hasGlobalFile){ + resourceUrl = factory.getResourceUrl(propertyFilenameProvider.get().getLocation()); + value = factory.getProperty(resourceUrl, resource.format(), resource.allowMissingResource(), propertyName, defaultValue); + } + return value; } } diff --git a/src/main/java/io/xlate/inject/PropertyResourceProducerBean.java b/src/main/java/io/xlate/inject/PropertyResourceProducerBean.java index 8d18832..deaf2d4 100644 --- a/src/main/java/io/xlate/inject/PropertyResourceProducerBean.java +++ b/src/main/java/io/xlate/inject/PropertyResourceProducerBean.java @@ -23,9 +23,11 @@ import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.inject.InjectionException; +import javax.enterprise.inject.Instance; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.Annotated; import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; @ApplicationScoped public class PropertyResourceProducerBean { @@ -35,6 +37,9 @@ public class PropertyResourceProducerBean { private final PropertyFactory factory = new PropertyFactory(); + @Inject + private Instance propertyFilenameProvider; + @Produces @Dependent @PropertyResource @@ -48,13 +53,22 @@ public Properties produceProperties(InjectionPoint point) { final Class beanType = point.getMember().getDeclaringClass(); final PropertyResource annotation = annotated.getAnnotation(PropertyResource.class); final PropertyResourceFormat format = annotation.format(); - URL resourceUrl = null; + Properties p = new Properties(); try { + URL resourceUrl = null; + boolean hasGlobalPropertyFile =propertyFilenameProvider != null && propertyFilenameProvider.isResolvable(); + if (hasGlobalPropertyFile) { + String globalFile = propertyFilenameProvider.get().getLocation(); + resourceUrl = factory.getResourceUrl(globalFile); + p.putAll(factory.getProperties(resourceUrl, format, annotation.allowMissingResource())); + } resourceUrl = factory.getResourceUrl(annotation, beanType); - return factory.getProperties(resourceUrl, format, annotation.allowMissingResource()); + p.putAll(factory.getProperties(resourceUrl, format, hasGlobalPropertyFile ? true: annotation.allowMissingResource())); + return p; } catch (Exception e) { throw new InjectionException(e); } } + } diff --git a/src/test/java/io/xlate/inject/PropertyProducerBeanIT.java b/src/test/java/io/xlate/inject/PropertyProducerBeanIT.java index 0c4c2c3..aef44a4 100644 --- a/src/test/java/io/xlate/inject/PropertyProducerBeanIT.java +++ b/src/test/java/io/xlate/inject/PropertyProducerBeanIT.java @@ -24,6 +24,7 @@ import org.jboss.weld.junit5.WeldInitiator; import org.jboss.weld.junit5.WeldJunit5Extension; import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.platform.runner.JUnitPlatform; @@ -35,7 +36,7 @@ class PropertyProducerBeanIT { @WeldSetup public WeldInitiator weld = WeldInitiator - .from(PropertyProducerBean.class) + .from(PropertyProducerBean.class, TestFileProvider.class) .build(); @Inject @Property @@ -78,6 +79,10 @@ class PropertyProducerBeanIT { @Property int int3; + @BeforeAll + public static void setUp() { + System.setProperty("string6.property.name", "string6value.system"); + } @Test void testString1_DefaultLookup() { assertEquals("string1value", string1); diff --git a/src/test/java/io/xlate/inject/PropertyProducerBeanTest.java b/src/test/java/io/xlate/inject/PropertyProducerBeanTest.java index 5373d4a..1224add 100644 --- a/src/test/java/io/xlate/inject/PropertyProducerBeanTest.java +++ b/src/test/java/io/xlate/inject/PropertyProducerBeanTest.java @@ -31,6 +31,7 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -41,6 +42,7 @@ import javax.enterprise.inject.spi.InjectionPoint; import javax.json.Json; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -61,11 +63,14 @@ class PropertyProducerBeanTest { @Mock PropertyResource defaultPropertyResource; + private Locale locale; @BeforeEach void setup() { bean = new PropertyProducerBean(); when(defaultPropertyResource.value()).thenReturn(""); when(defaultPropertyResource.format()).thenReturn(PropertyResourceFormat.PROPERTIES); + locale = Locale.getDefault(); + Locale.setDefault(Locale.ENGLISH); } private Property mockProperty(String name, @@ -959,4 +964,8 @@ void testProducePropertyJsonObjectInvalid() { }); } + @AfterEach + public void teardown(){ + Locale.setDefault(locale); + } } diff --git a/src/test/java/io/xlate/inject/PropertyResourceProducerBeanIT.java b/src/test/java/io/xlate/inject/PropertyResourceProducerBeanIT.java index 0ad61f0..4eafbb6 100644 --- a/src/test/java/io/xlate/inject/PropertyResourceProducerBeanIT.java +++ b/src/test/java/io/xlate/inject/PropertyResourceProducerBeanIT.java @@ -40,6 +40,7 @@ class PropertyResourceProducerBeanIT { .from(PropertyResourceProducerBean.class) .build(); + @Inject @PropertyResource Properties defaultProps; @@ -62,4 +63,5 @@ void testProps2() { assertEquals(1, props2.size()); assertEquals("true", props2.getProperty("value.is.found")); } + } diff --git a/src/test/java/io/xlate/inject/PropertyResourceProducerBeanTest.java b/src/test/java/io/xlate/inject/PropertyResourceProducerBeanTest.java index 6c7fa38..4be0f83 100644 --- a/src/test/java/io/xlate/inject/PropertyResourceProducerBeanTest.java +++ b/src/test/java/io/xlate/inject/PropertyResourceProducerBeanTest.java @@ -31,7 +31,10 @@ import javax.enterprise.inject.spi.InjectionPoint; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; @@ -140,6 +143,7 @@ void testEnvironmentVarForResourceLocation() { } @Test + @DisabledOnOs(OS.WINDOWS) void testProducePropertiesUnreadable() { File resource = new File("target/test-classes/io/xlate/inject/Unreadable.properties"); resource.setReadable(false); diff --git a/src/test/java/io/xlate/inject/PropertyResourceProducerBeanWithConfigurationFile.java b/src/test/java/io/xlate/inject/PropertyResourceProducerBeanWithConfigurationFile.java new file mode 100644 index 0000000..f6f97f8 --- /dev/null +++ b/src/test/java/io/xlate/inject/PropertyResourceProducerBeanWithConfigurationFile.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (C) 2018 xlate.io LLC, http://www.xlate.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + ******************************************************************************/ +package io.xlate.inject; + +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import javax.inject.Inject; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@RunWith(JUnitPlatform.class) +@ExtendWith(WeldJunit5Extension.class) +class PropertyResourceProducerBeanWithConfigurationFile { + + @WeldSetup + WeldInitiator weld = WeldInitiator + .from(PropertyResourceProducerBean.class, TestFileProvider.class) + .build(); + + + @Inject + @PropertyResource + Properties defaultProps; + + + @Inject + @PropertyResource("io/xlate/inject/PropertyResourceProducerBeanIT2.properties") + Properties props2; + + @Test + void testGlobalFile() { + WeldInitiator weld = WeldInitiator + .from(PropertyResourceProducerBean.class, TestFileProvider.class) + .build(); + assertNotNull(defaultProps); + System.out.println(defaultProps); + assertEquals(3, defaultProps.size()); + assertEquals("x", defaultProps.getProperty("key1")); + assertEquals("y", defaultProps.getProperty("key2")); + assertEquals("false", defaultProps.getProperty("value.is.found")); + } + + @Test + void testGlobalFileOverriddenByLocalLocation() { + WeldInitiator weld = WeldInitiator + .from(PropertyResourceProducerBean.class, TestFileProvider.class) + .build(); + assertNotNull(props2); + System.out.println(props2); + assertEquals(3, props2.size()); + assertEquals("true", props2.getProperty("value.is.found")); + assertEquals("x", props2.getProperty("key1")); + assertEquals("y", props2.getProperty("key2")); + } + + +} diff --git a/src/test/java/io/xlate/inject/TestFileProvider.java b/src/test/java/io/xlate/inject/TestFileProvider.java new file mode 100644 index 0000000..5aaa13b --- /dev/null +++ b/src/test/java/io/xlate/inject/TestFileProvider.java @@ -0,0 +1,15 @@ +package io.xlate.inject; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Default; + +@ApplicationScoped +@Default +public class TestFileProvider implements PropertyFileProvider { + + @Override + public String getLocation() { + return "classpath:global.properties"; + } + +} \ No newline at end of file diff --git a/src/test/resources/global.properties b/src/test/resources/global.properties new file mode 100644 index 0000000..cfae97b --- /dev/null +++ b/src/test/resources/global.properties @@ -0,0 +1,3 @@ +key1=x +key2=y +value.is.found=false \ No newline at end of file diff --git a/src/test/resources/io/xlate/inject/PropertyResourceProducerBeanWithGlobalConfiguration.properties b/src/test/resources/io/xlate/inject/PropertyResourceProducerBeanWithGlobalConfiguration.properties new file mode 100644 index 0000000..de075e5 --- /dev/null +++ b/src/test/resources/io/xlate/inject/PropertyResourceProducerBeanWithGlobalConfiguration.properties @@ -0,0 +1 @@ +value.is.found=in bean type based config file